Skip to content

Commit bbf6b35

Browse files
authored
Merge pull request Expensify#82985 from Expensify/cmartins-changeLinkForNonAdmin
Change link for non members
2 parents f8835d1 + 5d6ab15 commit bbf6b35

2 files changed

Lines changed: 51 additions & 6 deletions

File tree

src/libs/ModifiedExpenseMessage.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ function getMovedFromOrToReportMessage(translate: LocalizedTranslate, movedFromR
202202
}
203203
}
204204

205-
function getPolicyRulesModifiedMessage(translate: LocalizedTranslate, fields: PolicyRulesModifiedFields, policyID: string) {
206-
const route = `${environmentURL}/${ROUTES.WORKSPACE_RULES.getRoute(policyID)}`;
205+
function getPolicyRulesModifiedMessage(translate: LocalizedTranslate, fields: PolicyRulesModifiedFields, policyID: string, hasPolicyRuleAccess: boolean) {
206+
const route = hasPolicyRuleAccess ? `${environmentURL}/${ROUTES.WORKSPACE_RULES.getRoute(policyID)}` : CONST.CONFIGURE_EXPENSE_REPORT_RULES_HELP_URL;
207207
const entries = ObjectUtils.typedEntries(fields);
208208

209209
const fragments = entries.map(([key, value], i) => {
@@ -517,7 +517,10 @@ function getForReportAction({
517517

518518
if (policyRulesModifiedFields && rulePolicyID) {
519519
// eslint-disable-next-line @typescript-eslint/no-deprecated
520-
return getPolicyRulesModifiedMessage(translateLocal, policyRulesModifiedFields, rulePolicyID);
520+
const rulePolicy = getPolicy(rulePolicyID);
521+
const hasPolicyRuleAccess = !!rulePolicy?.areRulesEnabled && isPolicyAdmin(rulePolicy, storedCurrentUserLogin);
522+
// eslint-disable-next-line @typescript-eslint/no-deprecated
523+
return getPolicyRulesModifiedMessage(translateLocal, policyRulesModifiedFields, rulePolicyID, hasPolicyRuleAccess);
521524
}
522525
}
523526

@@ -756,11 +759,11 @@ function getForReportActionTemp({
756759

757760
const hasPolicyRulesModifiedFields = isReportActionOriginalMessageAnObject && 'policyRulesModifiedFields' in reportActionOriginalMessage && 'policyID' in reportActionOriginalMessage;
758761
if (hasPolicyRulesModifiedFields) {
759-
const rulePolicyID = reportActionOriginalMessage.policyID;
760762
const policyRulesModifiedFields = reportActionOriginalMessage.policyRulesModifiedFields;
761763

762-
if (policyRulesModifiedFields && rulePolicyID) {
763-
return getPolicyRulesModifiedMessage(translate, policyRulesModifiedFields, rulePolicyID);
764+
if (policyRulesModifiedFields && policy?.id) {
765+
const hasPolicyRuleAccess = !!policy?.areRulesEnabled && isPolicyAdmin(policy, currentUserLogin);
766+
return getPolicyRulesModifiedMessage(translate, policyRulesModifiedFields, policy?.id, hasPolicyRuleAccess);
764767
}
765768
}
766769

tests/unit/ModifiedExpenseMessageTest.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import {createRandomReport} from '../utils/collections/reports';
1414
import {translateLocal} from '../utils/TestHelper';
1515
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
1616

17+
// Mock PolicyUtils so getPolicy and isPolicyAdmin are controllable in tests. ModifiedExpenseMessage
18+
// uses named imports from this module; spies alone do not affect those references, so we need a module mock.
19+
jest.mock('@libs/PolicyUtils', () => ({
20+
...jest.requireActual<typeof PolicyUtils>('@libs/PolicyUtils'),
21+
getPolicy: jest.fn(),
22+
isPolicyAdmin: jest.fn(),
23+
}));
24+
1725
const MOVED_TO_REPORT_ID = '1';
1826
const MOVED_FROM_REPORT_ID = '2';
1927

@@ -814,10 +822,22 @@ describe('ModifiedExpenseMessage', () => {
814822

815823
describe('when policy rules modify an expense', () => {
816824
let environmentURL: string;
825+
const policyRulesPolicyId = '1234';
826+
817827
beforeAll(async () => {
818828
environmentURL = await getEnvironmentURL();
819829
});
820830

831+
beforeEach(() => {
832+
// Default: current user has policy rule access (admin + rules enabled), so link points to workspace rules
833+
// eslint-disable-next-line @typescript-eslint/no-deprecated
834+
(PolicyUtils.getPolicy as jest.Mock).mockReturnValue({
835+
id: policyRulesPolicyId,
836+
areRulesEnabled: true,
837+
} as Policy);
838+
(PolicyUtils.isPolicyAdmin as jest.Mock).mockReturnValue(true);
839+
});
840+
821841
it('returns the correct text message with multiple overrides', () => {
822842
const reportAction = {
823843
...createRandomReportAction(1),
@@ -924,6 +944,28 @@ describe('ModifiedExpenseMessage', () => {
924944

925945
expect(result).toEqual(expectedResult);
926946
});
947+
948+
it('returns the correct text message with help link for non-admin', () => {
949+
(PolicyUtils.isPolicyAdmin as jest.Mock).mockReturnValue(false);
950+
951+
const reportAction = {
952+
...createRandomReportAction(1),
953+
actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE,
954+
originalMessage: {
955+
policyID: policyRulesPolicyId,
956+
policyRulesModifiedFields: {
957+
category: 'Travel',
958+
merchant: "McDonald's",
959+
},
960+
} as OriginalMessageModifiedExpense,
961+
};
962+
963+
const result = getForReportAction({reportAction, policyID: report.policyID});
964+
965+
expect(result).toContain(CONST.CONFIGURE_EXPENSE_REPORT_RULES_HELP_URL);
966+
expect(result).toContain('workspace rules</a>');
967+
expect(result).toContain('set the category to "Travel" and merchant to "McDonald\'s"');
968+
});
927969
});
928970

929971
describe('when the category is changed without source (backward compatibility)', () => {

0 commit comments

Comments
 (0)