Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/components/ReportWelcomeText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) {
const isSelfDM = isSelfDMReportUtils(report);
const isInvoiceRoom = isInvoiceRoomReportUtils(report);
const isSystemChat = isSystemChatReportUtils(report);
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM || isSystemChat);
const participantAccountIDs = getParticipantsAccountIDsForDisplay(report, undefined, true, true, reportMetadata);
const moneyRequestOptions = temporary_getMoneyRequestOptions(report, policy, participantAccountIDs, isReportArchived, isRestrictedToPreferredPolicy);
const moneyRequestOptions = temporary_getMoneyRequestOptions(report, policy, participantAccountIDs, betas, isReportArchived, isRestrictedToPreferredPolicy);
const policyName = getPolicyName({report});

const filteredOptions = moneyRequestOptions.filter(
Expand Down
6 changes: 4 additions & 2 deletions src/libs/QuickActionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {Policy, Report} from '@src/types/onyx';
import type {Beta, Policy, Report} from '@src/types/onyx';
import type {QuickActionName} from '@src/types/onyx/QuickAction';
import type QuickAction from '@src/types/onyx/QuickAction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
Expand Down Expand Up @@ -99,6 +100,7 @@ const isQuickActionAllowed = (
quickActionReport: Report | undefined,
quickActionPolicy: Policy | undefined,
isReportArchived: boolean | undefined,
betas: OnyxEntry<Beta[]>,
isRestrictedToPreferredPolicy = false,
) => {
if (quickAction?.action === CONST.QUICK_ACTIONS.PER_DIEM) {
Expand All @@ -117,7 +119,7 @@ const isQuickActionAllowed = (
if (isReportHasManagerMCTest) {
return false;
}
return canCreateRequest(quickActionReport, quickActionPolicy, iouType, isReportArchived, isRestrictedToPreferredPolicy);
return canCreateRequest(quickActionReport, quickActionPolicy, iouType, isReportArchived, betas, isRestrictedToPreferredPolicy);
}
return true;
};
Expand Down
9 changes: 6 additions & 3 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9978,6 +9978,7 @@ function getMoneyRequestOptions(
report: OnyxEntry<Report>,
policy: OnyxEntry<Policy>,
reportParticipants: number[],
betas: OnyxEntry<Beta[]>,
filterDeprecatedTypes = false,
isReportArchived = false,
isRestrictedToPreferredPolicy = false,
Expand Down Expand Up @@ -10009,7 +10010,7 @@ function getMoneyRequestOptions(
if (
canRequestMoney(report, policy, otherParticipants) &&
reportParticipants.some((accountID) => accountID === CONST.ACCOUNT_ID.MANAGER_MCTEST) &&
Permissions.isBetaEnabled(CONST.BETAS.NEWDOT_MANAGER_MCTEST, allBetas)
Permissions.isBetaEnabled(CONST.BETAS.NEWDOT_MANAGER_MCTEST, betas)
) {
return [CONST.IOU.TYPE.SUBMIT];
}
Expand Down Expand Up @@ -10095,10 +10096,11 @@ function temporary_getMoneyRequestOptions(
report: OnyxEntry<Report>,
policy: OnyxEntry<Policy>,
reportParticipants: number[],
betas: OnyxEntry<Beta[]>,
isReportArchived = false,
isRestrictedToPreferredPolicy = false,
): Array<Exclude<IOUType, typeof CONST.IOU.TYPE.REQUEST | typeof CONST.IOU.TYPE.SEND | typeof CONST.IOU.TYPE.CREATE | typeof CONST.IOU.TYPE.SPLIT_EXPENSE>> {
return getMoneyRequestOptions(report, policy, reportParticipants, true, isReportArchived, isRestrictedToPreferredPolicy) as Array<
return getMoneyRequestOptions(report, policy, reportParticipants, betas, true, isReportArchived, isRestrictedToPreferredPolicy) as Array<
Exclude<IOUType, typeof CONST.IOU.TYPE.REQUEST | typeof CONST.IOU.TYPE.SEND | typeof CONST.IOU.TYPE.CREATE | typeof CONST.IOU.TYPE.SPLIT_EXPENSE>
>;
}
Expand Down Expand Up @@ -10308,6 +10310,7 @@ function canCreateRequest(
policy: OnyxEntry<Policy>,
iouType: ValueOf<typeof CONST.IOU.TYPE>,
isReportArchived: boolean | undefined,
betas: OnyxEntry<Beta[]>,
isRestrictedToPreferredPolicy = false,
): boolean {
const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number);
Expand All @@ -10316,7 +10319,7 @@ function canCreateRequest(
return false;
}

const requestOptions = getMoneyRequestOptions(report, policy, participantAccountIDs, false, isReportArchived, isRestrictedToPreferredPolicy);
const requestOptions = getMoneyRequestOptions(report, policy, participantAccountIDs, betas, false, isReportArchived, isRestrictedToPreferredPolicy);
requestOptions.push(CONST.IOU.TYPE.CREATE);

return requestOptions.includes(iouType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ function AttachmentPickerWithMenuItems({
],
};

const moneyRequestOptionsList = temporary_getMoneyRequestOptions(report, policy, reportParticipantIDs ?? [], isReportArchived, isRestrictedToPreferredPolicy).map(
const moneyRequestOptionsList = temporary_getMoneyRequestOptions(report, policy, reportParticipantIDs ?? [], betas, isReportArchived, isRestrictedToPreferredPolicy).map(
(option) => options[option],
);

Expand All @@ -308,6 +308,7 @@ function AttachmentPickerWithMenuItems({
showDelegateNoAccessModal,
translate,
icons,
betas,
]);

const createReportOption: PopoverMenuItem[] = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function ReportActionCompose({
const [initialModalState] = useOnyx(ONYXKEYS.MODAL, {canBeMissing: true});
const [newParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, {canBeMissing: true});
const [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, {canBeMissing: true});
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});

const shouldFocusComposerOnScreenFocus = shouldFocusInputOnScreenFocus || !!draftComment;

Expand Down Expand Up @@ -250,11 +251,11 @@ function ReportActionCompose({
const shouldDisplayDualDropZone = useMemo(() => {
const parentReport = getParentReport(report);
const isSettledOrApproved = isSettled(report) || isSettled(parentReport) || isReportApproved({report}) || isReportApproved({report: parentReport});
const hasMoneyRequestOptions = !!temporary_getMoneyRequestOptions(report, policy, reportParticipantIDs, isReportArchived, isRestrictedToPreferredPolicy).length;
const hasMoneyRequestOptions = !!temporary_getMoneyRequestOptions(report, policy, reportParticipantIDs, betas, isReportArchived, isRestrictedToPreferredPolicy).length;
const canModifyReceipt = shouldAddOrReplaceReceipt && !isSettledOrApproved;
const isRoomOrGroupChat = isChatRoom(report) || isGroupChat(report);
return !isRoomOrGroupChat && (canModifyReceipt || hasMoneyRequestOptions) && !isInvoiceReport(report);
}, [shouldAddOrReplaceReceipt, report, reportParticipantIDs, policy, isReportArchived, isRestrictedToPreferredPolicy]);
}, [shouldAddOrReplaceReceipt, report, reportParticipantIDs, policy, isReportArchived, isRestrictedToPreferredPolicy, betas]);

// Placeholder to display in the chat input.
const inputPlaceholder = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
};

if (quickAction?.action && quickActionReport) {
if (!isQuickActionAllowed(quickAction, quickActionReport, quickActionPolicy, isReportArchived, isRestrictedToPreferredPolicy)) {
if (!isQuickActionAllowed(quickAction, quickActionReport, quickActionPolicy, isReportArchived, allBetas, isRestrictedToPreferredPolicy)) {
return [];
}
const onSelected = () => {
Expand Down Expand Up @@ -554,6 +554,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
showDelegateNoAccessModal,
reportID,
allTransactionDrafts,
allBetas,
]);

const isTravelEnabled = useMemo(() => {
Expand Down
11 changes: 7 additions & 4 deletions src/pages/workspace/AccessOrNotFoundWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import type {Report} from '@src/types/onyx';
import type {Beta, Report} from '@src/types/onyx';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import type Policy from '@src/types/onyx/Policy';
import callOrReturn from '@src/types/utils/callOrReturn';
Expand All @@ -37,6 +37,7 @@ const ACCESS_VARIANTS = {
login: string,
report: OnyxEntry<Report>,
allPolicies: NonNullable<OnyxCollection<Policy>> | null,
betas: OnyxEntry<Beta[]>,
iouType?: IOUType,
isReportArchived?: boolean,
isRestrictedToPreferredPolicy?: boolean,
Expand All @@ -45,7 +46,7 @@ const ACCESS_VARIANTS = {
isValidMoneyRequestType(iouType) &&
// Allow the user to submit the expense if we are submitting the expense in global menu or the report can create the expense

(isEmptyObject(report?.reportID) || canCreateRequest(report, policy, iouType, isReportArchived, isRestrictedToPreferredPolicy)) &&
(isEmptyObject(report?.reportID) || canCreateRequest(report, policy, iouType, isReportArchived, betas, isRestrictedToPreferredPolicy)) &&
(iouType !== CONST.IOU.TYPE.INVOICE || canSendInvoice(allPolicies, login)),
} as const satisfies Record<
string,
Expand All @@ -54,6 +55,7 @@ const ACCESS_VARIANTS = {
login: string,
report: Report,
allPolicies: NonNullable<OnyxCollection<Policy>> | null,
betas: OnyxEntry<Beta[]>,
iouType?: IOUType,
isArchivedReport?: boolean,
isRestrictedToPreferredPolicy?: boolean,
Expand Down Expand Up @@ -148,6 +150,7 @@ function AccessOrNotFoundWrapper({
const [isLoadingReportData = true] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {canBeMissing: true});
const {login = ''} = useCurrentUserPersonalDetails();
const {isRestrictedToPreferredPolicy} = usePreferredPolicy();
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
const isPolicyIDInRoute = !!policyID?.length;
const isMoneyRequest = !!iouType && isValidMoneyRequestType(iouType);
const isFromGlobalCreate = !!reportID && isEmptyObject(report?.reportID);
Expand All @@ -174,9 +177,9 @@ function AccessOrNotFoundWrapper({
const isPageAccessible = accessVariants.reduce((acc, variant) => {
const accessFunction = ACCESS_VARIANTS[variant];
if (variant === CONST.IOU.ACCESS_VARIANTS.CREATE) {
return acc && accessFunction(policy, login, report, allPolicies ?? null, iouType, isReportArchived, isRestrictedToPreferredPolicy);
return acc && accessFunction(policy, login, report, allPolicies ?? null, betas, iouType, isReportArchived, isRestrictedToPreferredPolicy);
}
return acc && accessFunction(policy, login, report, allPolicies ?? null, iouType, isReportArchived);
return acc && accessFunction(policy, login, report, allPolicies ?? null, betas, iouType, isReportArchived);
}, true);

const isPolicyNotAccessible = !isPolicyAccessible(policy, login);
Expand Down
2 changes: 1 addition & 1 deletion tests/perf-test/ReportUtils.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ describe('ReportUtils', () => {
const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1);

await waitForBatchedUpdates();
await measureFunction(() => temporary_getMoneyRequestOptions(report, policy, reportParticipants));
await measureFunction(() => temporary_getMoneyRequestOptions(report, policy, reportParticipants, [CONST.BETAS.ALL]));
});

test('[ReportUtils] getWorkspaceChat on 1k policies', async () => {
Expand Down
56 changes: 42 additions & 14 deletions tests/unit/QuickActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// eslint-disable-next-line no-restricted-syntax
import * as PolicyUtils from '@libs/PolicyUtils';
import {isQuickActionAllowed} from '@libs/QuickActionUtils';
import * as ReportUtils from '@libs/ReportUtils';

Check failure on line 6 in tests/unit/QuickActionUtilsTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"

Check failure on line 6 in tests/unit/QuickActionUtilsTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, Report} from '@src/types/onyx';
Expand Down Expand Up @@ -41,11 +42,38 @@
mockedPolicyUtils.shouldShowPolicy.mockReturnValue(false);

// When the report contains Manager McTest
const result = isQuickActionAllowed(requestScanAction, reportWithManagerMcTest, undefined, undefined);
const result = isQuickActionAllowed(requestScanAction, reportWithManagerMcTest, undefined, undefined, [CONST.BETAS.ALL]);

// Then it should return false
expect(result).toBe(false);
});

it('forwards betas to canCreateRequest and respects the result', () => {
const quickAction = {
action: CONST.QUICK_ACTIONS.REQUEST_MANUAL,
isFirstQuickAction: false,
};
const ACCOUNT_ONE = 1;
const ACCOUNT_TWO = 2;
const report: Report = {
reportID: 'forward-betas',
type: CONST.REPORT.TYPE.CHAT,
participants: {
[ACCOUNT_ONE]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
[ACCOUNT_TWO]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
},
};
const betas = [CONST.BETAS.NEWDOT_MANAGER_MCTEST];
const canCreateRequestSpy = jest.spyOn(ReportUtils, 'canCreateRequest').mockReturnValue(false);

const result = isQuickActionAllowed(quickAction, report, undefined, false, betas, false);

expect(result).toBe(false);
expect(canCreateRequestSpy).toHaveBeenCalledWith(report, undefined, CONST.IOU.TYPE.SUBMIT, false, betas, false);
expect(canCreateRequestSpy).toHaveBeenCalledTimes(1);

canCreateRequestSpy.mockRestore();
});
});

describe('Preferred policy restrictions', () => {
Expand Down Expand Up @@ -93,24 +121,24 @@
};

it('should restrict REQUEST action on DMs', () => {
const withoutRestrictionsResult = isQuickActionAllowed(requestManualAction, DMReport, undefined, false, false);
const withRestrictionsResult = isQuickActionAllowed(requestManualAction, DMReport, undefined, false, true);
const withoutRestrictionsResult = isQuickActionAllowed(requestManualAction, DMReport, undefined, false, [CONST.BETAS.ALL], false);
const withRestrictionsResult = isQuickActionAllowed(requestManualAction, DMReport, undefined, false, [CONST.BETAS.ALL], true);

expect(withoutRestrictionsResult).toBe(true);
expect(withRestrictionsResult).toBe(false);
});

it('should restrict SPLIT action on DMs', () => {
const withoutRestrictionsResult = isQuickActionAllowed(splitManualAction, DMReport, undefined, false, false);
const withRestrictionsResult = isQuickActionAllowed(splitManualAction, DMReport, undefined, false, true);
const withoutRestrictionsResult = isQuickActionAllowed(splitManualAction, DMReport, undefined, false, [CONST.BETAS.ALL], false);
const withRestrictionsResult = isQuickActionAllowed(splitManualAction, DMReport, undefined, false, [CONST.BETAS.ALL], true);

expect(withoutRestrictionsResult).toBe(true);
expect(withRestrictionsResult).toBe(false);
});

it('should restrict SEND_MONEY action on DMs', () => {
const withoutRestrictionsResult = isQuickActionAllowed(sendMoneyAction, DMReport, undefined, false, false);
const withRestrictionsResult = isQuickActionAllowed(sendMoneyAction, DMReport, undefined, false, true);
const withoutRestrictionsResult = isQuickActionAllowed(sendMoneyAction, DMReport, undefined, false, [CONST.BETAS.ALL], false);
const withRestrictionsResult = isQuickActionAllowed(sendMoneyAction, DMReport, undefined, false, [CONST.BETAS.ALL], true);

expect(withoutRestrictionsResult).toBe(true);
expect(withRestrictionsResult).toBe(false);
Expand All @@ -119,8 +147,8 @@
it('should restrict SPLIT action on Group chats', () => {
const groupChatReport: Report = LHNTestUtils.getFakeReport([1, 2, 3, 4]);

const withoutRestrictionsResult = isQuickActionAllowed(splitManualAction, groupChatReport, undefined, false, false);
const withRestrictionsResult = isQuickActionAllowed(splitManualAction, groupChatReport, undefined, false, true);
const withoutRestrictionsResult = isQuickActionAllowed(splitManualAction, groupChatReport, undefined, false, [CONST.BETAS.ALL], false);
const withRestrictionsResult = isQuickActionAllowed(splitManualAction, groupChatReport, undefined, false, [CONST.BETAS.ALL], true);

expect(withoutRestrictionsResult).toBe(true);
expect(withRestrictionsResult).toBe(false);
Expand All @@ -132,8 +160,8 @@
chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
};

const withoutRestrictionsResult = isQuickActionAllowed(splitManualAction, policyRoomReport, undefined, false, false);
const withRestrictionsResult = isQuickActionAllowed(splitManualAction, policyRoomReport, undefined, false, true);
const withoutRestrictionsResult = isQuickActionAllowed(splitManualAction, policyRoomReport, undefined, false, [CONST.BETAS.ALL], false);
const withRestrictionsResult = isQuickActionAllowed(splitManualAction, policyRoomReport, undefined, false, [CONST.BETAS.ALL], true);

expect(withoutRestrictionsResult).toBe(true);
expect(withRestrictionsResult).toBe(false);
Expand Down Expand Up @@ -187,22 +215,22 @@
} as unknown as Policy;
mockedPolicyUtils.isPaidGroupPolicy.mockReturnValue(true);

expect(isQuickActionAllowed(perDiemAction, report, policy, false, false)).toBe(true);
expect(isQuickActionAllowed(perDiemAction, report, policy, false, [CONST.BETAS.ALL], false)).toBe(true);
});
it("should not allow per diem action when policy doesn't have per diem rates", () => {
mockedPolicyUtils.getPerDiemCustomUnit.mockReturnValue(undefined);
const policy = {
id: '1',
arePerDiemRatesEnabled: true,
} as unknown as Policy;
expect(isQuickActionAllowed(perDiemAction, report, policy, false, false)).toBe(false);
expect(isQuickActionAllowed(perDiemAction, report, policy, false, [CONST.BETAS.ALL], false)).toBe(false);
});
it("should not allow per diem action when policy doesn't have per diem enabled", () => {
const policy = {
id: '1',
arePerDiemRatesEnabled: false,
} as unknown as Policy;
expect(isQuickActionAllowed(perDiemAction, report, policy, false, false)).toBe(false);
expect(isQuickActionAllowed(perDiemAction, report, policy, false, [CONST.BETAS.ALL], false)).toBe(false);
});
});
});
Expand Down
Loading
Loading