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
16 changes: 7 additions & 9 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@
},
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';

Check warning on line 901 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (value) => {
Expand All @@ -906,7 +906,7 @@
if (!value) {
return;
}

Check warning on line 909 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
currentUserEmail = value.email;
currentUserAccountID = value.accountID;
isAnonymousUser = value.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS;
Expand All @@ -924,7 +924,7 @@
currentUserPersonalDetails = value?.[currentUserAccountID] ?? undefined;
}
allPersonalDetails = value ?? {};
allPersonalDetailLogins = Object.values(allPersonalDetails).map((personalDetail) => personalDetail?.login ?? '');

Check warning on line 927 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
},
});

Expand All @@ -936,14 +936,14 @@
});

let allPolicies: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 939 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => (allPolicies = value),
});

let allReports: OnyxCollection<Report>;
let reportsByPolicyID: ReportByPolicyMap;

Check warning on line 946 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
Expand All @@ -951,7 +951,7 @@
allReports = value;
UnreadIndicatorUpdaterHelper().then((module) => {
module.triggerUnreadUpdate();
});

Check warning on line 954 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

if (!value) {
return;
Expand Down Expand Up @@ -992,14 +992,14 @@

let allTransactions: OnyxCollection<Transaction> = {};
let reportsTransactions: Record<string, Transaction[]> = {};
Onyx.connect({

Check warning on line 995 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
if (!value) {
return;
}
allTransactions = Object.fromEntries(Object.entries(value).filter(([, transaction]) => transaction));

Check warning on line 1002 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

reportsTransactions = Object.values(value).reduce<Record<string, Transaction[]>>((all, transaction) => {
const reportsMap = all;
Expand All @@ -1025,7 +1025,7 @@
if (!actions) {
return;
}
allReportActions = actions;

Check warning on line 1028 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
},
});

Expand All @@ -1038,7 +1038,7 @@
if (!value) {
return;
}
allReportMetadata = value;

Check warning on line 1041 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function

Object.entries(value).forEach(([reportID, reportMetadata]) => {
if (!reportMetadata) {
Expand Down Expand Up @@ -7869,11 +7869,9 @@
* Assuming the passed in report is a default room, lets us know whether we can see it or not, based on permissions and
* the various subsets of users we've allowed to use default rooms.
*/
function canSeeDefaultRoom(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>): boolean {
function canSeeDefaultRoom(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, isReportArchived = false): boolean {
// Include archived rooms
// This will get removed as part of https://github.com/Expensify/App/issues/59961
// eslint-disable-next-line deprecation/deprecation
if (isArchivedNonExpenseReport(report, !!getReportNameValuePairs(report?.reportID)?.private_isArchived)) {
if (isArchivedNonExpenseReport(report, isReportArchived)) {
return true;
}

Expand All @@ -7891,9 +7889,9 @@
return Permissions.isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS, betas ?? []);
}

function canAccessReport(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>): boolean {
function canAccessReport(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, isReportArchived = false): boolean {
// We hide default rooms (it's basically just domain rooms now) from people who aren't on the defaultRooms beta.
if (isDefaultRoom(report) && !canSeeDefaultRoom(report, betas)) {
if (isDefaultRoom(report) && !canSeeDefaultRoom(report, betas, isReportArchived)) {
return false;
}

Expand Down Expand Up @@ -8204,7 +8202,7 @@
return null;
}

if (!canAccessReport(report, betas)) {
if (!canAccessReport(report, betas, isReportArchived)) {
return null;
}

Expand Down Expand Up @@ -9296,8 +9294,8 @@
/**
* Check to see if the current user has access to view the report.
*/
function canCurrentUserOpenReport(report: OnyxEntry<Report>): boolean {
return (isReportParticipant(currentUserAccountID, report) || isPublicRoom(report)) && canAccessReport(report, allBetas);
function canCurrentUserOpenReport(report: OnyxEntry<Report>, isReportArchived = false): boolean {
return (isReportParticipant(currentUserAccountID, report) || isPublicRoom(report)) && canAccessReport(report, allBetas, isReportArchived);
}

function shouldUseFullTitleToDisplay(report: OnyxEntry<Report>): boolean {
Expand Down
23 changes: 20 additions & 3 deletions src/pages/home/report/ReportActionItemParentAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {View} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import onyxSubscribe from '@libs/onyxSubscribe';
Expand All @@ -13,6 +14,7 @@ import {
canUserPerformWriteAction as canUserPerformWriteActionReportUtils,
getAllAncestorReportActionIDs,
getAllAncestorReportActions,
isArchivedReport,
navigateToLinkedReportAction,
} from '@libs/ReportUtils';
import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report';
Expand Down Expand Up @@ -81,6 +83,19 @@ function ReportActionItemParentAction({
const [allAncestors, setAllAncestors] = useState<Ancestor[]>([]);
const {isOffline} = useNetwork();
const {isInNarrowPaneModal} = useResponsiveLayout();
const [ancestorReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
canBeMissing: true,
selector: (allPairs) => {
const ancestorIDsToSelect = new Set(ancestorIDs.current.reportIDs);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be moved outside of the selector so that it doesn't have to run on every iteration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tgolen If we move that logic outside of the selector, we’ll hit an ESLint error:

Ref values (the current property) may not be accessed during render. (https://react.dev/reference/react/useRef) eslint(react-compiler/react-compiler)

To work around this, we’d need to add: // eslint-disable-next-line react-compiler/react-compiler

Do you still think it’s worth moving the logic outside of the selector and using the ESLint disable comment?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it is fine to leave it


return Object.fromEntries(
Object.entries(allPairs ?? {}).filter(([key]) => {
const id = key.split('_').at(1);
return id && ancestorIDsToSelect.has(id);
}),
);
},
});

useEffect(() => {
const unsubscribeReports: Array<() => void> = [];
Expand Down Expand Up @@ -124,7 +139,9 @@ function ReportActionItemParentAction({
const ancestorReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${ancestor.report.reportID}`];
const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(ancestorReport);
const shouldDisplayThreadDivider = !isTripPreview(ancestor.reportAction);

const reportNameValuePair =
ancestorReportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${ancestorReports.current?.[ancestor?.report?.reportID]?.reportID}`];
const isAncestorReportArchived = isArchivedReport(reportNameValuePair);
return (
<OfflineWithFeedback
key={ancestor.reportAction.reportActionID}
Expand All @@ -137,14 +154,14 @@ function ReportActionItemParentAction({
{shouldDisplayThreadDivider && (
<ThreadDivider
ancestor={ancestor}
isLinkDisabled={!canCurrentUserOpenReport(ancestorReports.current?.[ancestor?.report?.reportID])}
isLinkDisabled={!canCurrentUserOpenReport(ancestorReports.current?.[ancestor?.report?.reportID], isAncestorReportArchived)}
/>
)}
<ReportActionItem
allReports={allReports}
policies={policies}
onPress={
canCurrentUserOpenReport(ancestorReports.current?.[ancestor?.report?.reportID])
canCurrentUserOpenReport(ancestorReports.current?.[ancestor?.report?.reportID], isAncestorReportArchived)
? () => navigateToLinkedReportAction(ancestor, isInNarrowPaneModal, canUserPerformWriteAction, isOffline)
: undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, {useEffect, useMemo} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import {openReport} from '@libs/actions/Report';
import getComponentDisplayName from '@libs/getComponentDisplayName';
Expand Down Expand Up @@ -83,7 +84,8 @@ export default function <TProps extends WithReportAndReportActionOrNotFoundProps
// Perform all the loading checks
const isLoadingReport = isLoadingReportData && !report?.reportID;
const isLoadingReportAction = isEmptyObject(reportActions) || (reportMetadata?.isLoadingInitialReportActions && isEmptyObject(linkedReportAction));
const shouldHideReport = !isLoadingReport && (!report?.reportID || !canAccessReport(report, betas));
const isReportArchived = useReportIsArchived(report?.reportID);
const shouldHideReport = !isLoadingReport && (!report?.reportID || !canAccessReport(report, betas, isReportArchived));

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if ((isLoadingReport || isLoadingReportAction) && !shouldHideReport) {
Expand Down
5 changes: 3 additions & 2 deletions src/pages/home/report/withReportOrNotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, {useEffect} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
import {openReport} from '@libs/actions/Report';
import getComponentDisplayName from '@libs/getComponentDisplayName';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function (
const contentShown = React.useRef(false);
const isReportIdInRoute = !!props.route.params.reportID?.length;
const isReportLoaded = !isEmptyObject(report) && !!report?.reportID;

const isReportArchived = useReportIsArchived(report?.reportID);
// The `isLoadingInitialReportActions` value will become `false` only after the first OpenReport API call is finished (either succeeded or failed)
const shouldFetchReport = isReportIdInRoute && reportMetadata?.isLoadingInitialReportActions !== false;

Expand All @@ -90,7 +91,7 @@ export default function (

if (shouldRequireReportID || isReportIdInRoute) {
const shouldShowFullScreenLoadingIndicator = !isReportLoaded && (isLoadingReportData !== false || shouldFetchReport);
const shouldShowNotFoundPage = !isReportLoaded || !canAccessReport(report, betas);
const shouldShowNotFoundPage = !isReportLoaded || !canAccessReport(report, betas, isReportArchived);

// If the content was shown, but it's not anymore, that means the report was deleted, and we are probably navigating out of this screen.
// Return null for this case to avoid rendering FullScreenLoadingIndicator or NotFoundPage when animating transition.
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
canHoldUnholdReportAction,
canJoinChat,
canLeaveChat,
canSeeDefaultRoom,
canUserPerformWriteAction,
findLastAccessedReport,
getAllAncestorReportActions,
Expand Down Expand Up @@ -227,6 +228,10 @@ const personalDetails: PersonalDetailsList = {
accountID: 7,
login: 'owner@test.com',
},
'8': {
accountID: 8,
login: CONST.EMAIL.GUIDES_DOMAIN,
},
};

const rules = {
Expand Down Expand Up @@ -4916,4 +4921,35 @@ describe('ReportUtils', () => {
expect(getReportStatusTranslation(undefined, CONST.REPORT.STATUS_NUM.OPEN)).toBe('');
});
});
describe('canSeeDefaultRoom', () => {
it('should return true if report is archived room ', () => {
const betas = [CONST.BETAS.DEFAULT_ROOMS];
const report: Report = {
...createRandomReport(40002),
type: CONST.REPORT.TYPE.CHAT,
participants: buildParticipantsFromAccountIDs([currentUserAccountID, 1]),
};
expect(canSeeDefaultRoom(report, betas, true)).toBe(true);
});
it('should return true if the room has an assigned guide', () => {
const betas = [CONST.BETAS.DEFAULT_ROOMS];
const report: Report = {
...createRandomReport(40002),
participants: buildParticipantsFromAccountIDs([currentUserAccountID, 8]),
};
Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails).then(() => {
expect(canSeeDefaultRoom(report, betas, false)).toBe(true);
});
});
it('should return true if the report is admin room', () => {
const betas = [CONST.BETAS.DEFAULT_ROOMS];
const report: Report = {
...createRandomReport(40002),
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS,
};
Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails).then(() => {
expect(canSeeDefaultRoom(report, betas, false)).toBe(true);
});
});
});
});
Loading