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
13 changes: 9 additions & 4 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8611,7 +8611,12 @@ function buildOptimisticCardAssignedReportAction(assigneeAccountID: number, curr
};
}

function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: number, currentUserAccountID: number, delegateEmailParam: string | undefined): OptimisticEditedTaskReportAction {
function buildOptimisticChangedTaskAssigneeReportAction(
assigneeAccountID: number,
currentUserAccountID: number,
delegateEmailParam: string | undefined,
translate: LocalizedTranslate,
): OptimisticEditedTaskReportAction {
const delegateAccountDetails = delegateEmailParam ? getPersonalDetailByEmail(delegateEmailParam) : undefined;

return {
Expand All @@ -8622,7 +8627,7 @@ function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: numbe
message: [
{
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
text: `assigned to ${getDisplayNameForParticipant({accountID: assigneeAccountID, formatPhoneNumber: formatPhoneNumberPhoneUtils})}`,
text: `assigned to ${getDisplayNameForParticipant({accountID: assigneeAccountID, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate})}`,
html: `assigned to <mention-user accountID="${assigneeAccountID}"/>`,
},
],
Expand Down Expand Up @@ -8752,7 +8757,7 @@ function buildOptimisticResolvedDuplicatesReportAction(): OptimisticDismissedVio
};
}

function buildOptimisticChangeApproverReportAction(managerID: number, actorAccountID: number): OptimisticChangedApproverReportAction {
function buildOptimisticChangeApproverReportAction(managerID: number, actorAccountID: number, translate: LocalizedTranslate): OptimisticChangedApproverReportAction {
const created = DateUtils.getDBTime();
return {
actionName: managerID === actorAccountID ? CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL : CONST.REPORT.ACTIONS.TYPE.REROUTE,
Expand All @@ -8762,7 +8767,7 @@ function buildOptimisticChangeApproverReportAction(managerID: number, actorAccou
message: [
{
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
text: `changed the approver to ${getDisplayNameForParticipant({accountID: managerID, formatPhoneNumber: formatPhoneNumberPhoneUtils})}`,
text: `changed the approver to ${getDisplayNameForParticipant({accountID: managerID, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate})}`,
html: `changed the approver to <mention-user accountID="${managerID}"/>`,
},
],
Expand Down
46 changes: 32 additions & 14 deletions src/libs/actions/IOU/ReportWorkflow.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {LocalizedTranslate} from '@components/LocaleContextProvider';

import * as API from '@libs/API';
import type {
AddReportApproverParams,
Expand Down Expand Up @@ -1731,8 +1733,9 @@ function assignReportToMe(
isASAPSubmitBetaEnabled: boolean,
reportCurrentNextStepDeprecated: OnyxEntry<OnyxTypes.ReportNextStepDeprecated>,
isTrackIntentUser: boolean | undefined,
translate: LocalizedTranslate,
) {
const takeControlReportAction = buildOptimisticChangeApproverReportAction(accountID, accountID);
const takeControlReportAction = buildOptimisticChangeApproverReportAction(accountID, accountID, translate);

// buildOptimisticNextStep is used in parallel
const optimisticNextStepDeprecated = buildNextStepNew({
Expand Down Expand Up @@ -1838,19 +1841,34 @@ function assignReportToMe(
API.write(WRITE_COMMANDS.ASSIGN_REPORT_TO_ME, params, onyxData);
}

function addReportApprover(
report: OnyxTypes.Report,
newApproverEmail: string,
newApproverAccountID: number,
accountID: number,
email: string,
policy: OnyxEntry<OnyxTypes.Policy>,
hasViolations: boolean,
isASAPSubmitBetaEnabled: boolean,
reportCurrentNextStepDeprecated: OnyxEntry<OnyxTypes.ReportNextStepDeprecated>,
isTrackIntentUser: boolean | undefined,
) {
const takeControlReportAction = buildOptimisticChangeApproverReportAction(newApproverAccountID, accountID);
type AddReportApproverOptions = {
report: OnyxTypes.Report;
newApproverEmail: string;
newApproverAccountID: number;
accountID: number;
email: string;
policy: OnyxEntry<OnyxTypes.Policy>;
hasViolations: boolean;
isASAPSubmitBetaEnabled: boolean;
reportCurrentNextStepDeprecated: OnyxEntry<OnyxTypes.ReportNextStepDeprecated>;
isTrackIntentUser: boolean | undefined;
translate: LocalizedTranslate;
};

function addReportApprover({
report,
newApproverEmail,
newApproverAccountID,
accountID,
email,
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportCurrentNextStepDeprecated,
isTrackIntentUser,
translate,
}: AddReportApproverOptions) {
const takeControlReportAction = buildOptimisticChangeApproverReportAction(newApproverAccountID, accountID, translate);

// buildOptimisticNextStep is used in parallel
const optimisticNextStepDeprecated = buildNextStepNew({
Expand Down
4 changes: 3 additions & 1 deletion src/libs/actions/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type EditTaskAssigneeOptions = {
assigneeAccountID?: number | null;
assigneeChatReport?: OnyxEntry<OnyxTypes.Report>;
isOptimisticReport?: boolean;
translate: LocalizedTranslate;
};

type OptimisticReport = Pick<OnyxTypes.Report, 'reportName' | 'managerID' | 'pendingFields' | 'participants'>;
Expand Down Expand Up @@ -788,9 +789,10 @@ function editTaskAssignee({
assigneeAccountID = 0,
assigneeChatReport,
isOptimisticReport,
translate,
}: EditTaskAssigneeOptions) {
// Create the EditedReportAction on the task
const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID ?? CONST.DEFAULT_NUMBER_ID, currentUserAccountID, delegateEmail);
const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID ?? CONST.DEFAULT_NUMBER_ID, currentUserAccountID, delegateEmail, translate);
const reportName = report.reportName?.trim();

let assigneeChatReportOnyxData;
Expand Down
4 changes: 2 additions & 2 deletions src/pages/DynamicReportChangeApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ function DynamicReportChangeApproverPage({report, policy, isLoadingReportData}:
Navigation.navigate(ROUTES.REPORT_CHANGE_APPROVER_ADD_APPROVER.getRoute(report.reportID));
return;
}
assignReportToMe(report, currentUserDetails.accountID, currentUserDetails.email ?? '', policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser);
assignReportToMe(report, currentUserDetails.accountID, currentUserDetails.email ?? '', policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser, translate);
Navigation.dismissToPreviousRHP();
}, [selectedApproverType, report, currentUserDetails.accountID, currentUserDetails.email, policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser]);
}, [selectedApproverType, report, currentUserDetails.accountID, currentUserDetails.email, policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser, translate]);

const approverTypes = useMemo(() => {
const data: Array<ListItem<ApproverType>> = [
Expand Down
15 changes: 8 additions & 7 deletions src/pages/ReportAddApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,19 @@ function ReportAddApproverPage({report, isLoadingReportData, policy}: ReportAddA
return;
}
startWithLoading(() => {
addReportApprover(
addReportApprover({
report,
selectedApproverEmail,
Number(employeeAccountID),
currentUserDetails.accountID,
currentUserDetails.email ?? '',
newApproverEmail: selectedApproverEmail,
newApproverAccountID: Number(employeeAccountID),
accountID: currentUserDetails.accountID,
email: currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
reportCurrentNextStepDeprecated: reportNextStep,
isTrackIntentUser,
);
translate,
});
Navigation.dismissToPreviousRHP();
});
};
Expand Down
15 changes: 8 additions & 7 deletions src/pages/Search/SearchAddApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,19 @@ function SearchAddApproverPage() {

const hasViolations = hasViolationsReportUtils(report.reportID, transactionViolations, currentUserDetails.accountID, currentUserDetails.email ?? '');
const reportNextStep = allReportNextSteps?.[`${ONYXKEYS.COLLECTION.NEXT_STEP}${selectedReport.reportID}`];
addReportApprover(
addReportApprover({
report,
selectedApproverEmail,
Number(employeeAccountID),
currentUserDetails.accountID,
currentUserDetails.email ?? '',
newApproverEmail: selectedApproverEmail,
newApproverAccountID: Number(employeeAccountID),
accountID: currentUserDetails.accountID,
email: currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
reportCurrentNextStepDeprecated: reportNextStep,
isTrackIntentUser,
);
translate,
});
}

// Note: This clears both reports and transactions
Expand Down
12 changes: 11 additions & 1 deletion src/pages/Search/SearchChangeApproverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,17 @@ function SearchChangeApproverPage() {
if (report.managerID !== currentUserDetails.accountID) {
const hasViolations = hasViolationsReportUtils(report.reportID, transactionViolations, currentUserDetails.accountID, currentUserDetails.email ?? '');
const reportNextStep = allReportNextSteps?.[`${ONYXKEYS.COLLECTION.NEXT_STEP}${selectedReport.reportID}`];
assignReportToMe(report, currentUserDetails.accountID, currentUserDetails.email ?? '', policy, hasViolations, isASAPSubmitBetaEnabled, reportNextStep, isTrackIntentUser);
assignReportToMe(
report,
currentUserDetails.accountID,
currentUserDetails.email ?? '',
policy,
hasViolations,
isASAPSubmitBetaEnabled,
reportNextStep,
isTrackIntentUser,
translate,
);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/pages/tasks/DynamicTaskAssigneeSelectorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ function DynamicTaskAssigneeSelectorModal() {
assigneeAccountID: option?.accountID,
assigneeChatReport,
isOptimisticReport,
translate,
});
}
Navigation.goBack(backPath);
Expand Down
2 changes: 2 additions & 0 deletions tests/actions/TaskTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,7 @@ describe('actions/Task', () => {
hasOutstandingChildTask: false,
delegateEmail: DELEGATE_EMAIL,
assigneeAccountID: ASSIGNEE_ACCOUNT_ID,
translate: translateLocal,
});

const calls = mockWrite.mock.calls;
Expand Down Expand Up @@ -1414,6 +1415,7 @@ describe('actions/Task', () => {
hasOutstandingChildTask: false,
delegateEmail: undefined,
assigneeAccountID: ASSIGNEE_ACCOUNT_ID,
translate: translateLocal,
});

const calls = mockWrite.mock.calls;
Expand Down
50 changes: 50 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
buildOptimisticApprovedReportAction,
buildOptimisticCancelPaymentReportAction,
buildOptimisticCardAssignedReportAction,
buildOptimisticChangeApproverReportAction,
buildOptimisticChangedTaskAssigneeReportAction,
buildOptimisticChatReport,
buildOptimisticClosedReportAction,
buildOptimisticCreatedReportAction,
Expand Down Expand Up @@ -19938,6 +19940,54 @@ describe('ReportUtils', () => {
});
});

describe('buildOptimisticChangedTaskAssigneeReportAction', () => {
it('resolves the assignee display name through the injected translate function', async () => {
// A nameless assignee falls back to the "hidden" copy, which is produced by the injected translate
const hiddenAssigneeAccountID = 445566;
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[hiddenAssigneeAccountID]: {accountID: hiddenAssigneeAccountID, login: '', displayName: ''},
});
await waitForBatchedUpdates();

const translateWithMarker: LocalizedTranslate = (path, ...parameters) => (path === 'common.hidden' ? 'HiddenAssigneeMarker' : translateLocal(path, ...parameters));

const action = buildOptimisticChangedTaskAssigneeReportAction(hiddenAssigneeAccountID, currentUserAccountID, undefined, translateWithMarker);

// The message text resolves the assignee through the provided translate, proving the param drives output
const [message] = Array.isArray(action.message) ? action.message : [];
expect(message?.text).toBe('assigned to HiddenAssigneeMarker');
expect(action.actionName).toBe(CONST.REPORT.ACTIONS.TYPE.TASK_EDITED);
expect(action.actorAccountID).toBe(currentUserAccountID);
});
});

describe('buildOptimisticChangeApproverReportAction', () => {
it('resolves the new approver display name through the injected translate function', async () => {
// A nameless manager falls back to the "hidden" copy, which is produced by the injected translate
const hiddenManagerAccountID = 445577;
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[hiddenManagerAccountID]: {accountID: hiddenManagerAccountID, login: '', displayName: ''},
});
await waitForBatchedUpdates();

const translateWithMarker: LocalizedTranslate = (path, ...parameters) => (path === 'common.hidden' ? 'HiddenApproverMarker' : translateLocal(path, ...parameters));

const action = buildOptimisticChangeApproverReportAction(hiddenManagerAccountID, currentUserAccountID, translateWithMarker);

// The message text resolves the manager through the provided translate, proving the param drives output
const [message] = Array.isArray(action.message) ? action.message : [];
expect(message?.text).toBe('changed the approver to HiddenApproverMarker');
// The actor differs from the new approver, so this is a reroute rather than taking control
expect(action.actionName).toBe(CONST.REPORT.ACTIONS.TYPE.REROUTE);
});

it('marks the action as TAKE_CONTROL when the actor assigns themselves', () => {
const action = buildOptimisticChangeApproverReportAction(currentUserAccountID, currentUserAccountID, translateLocal);

expect(action.actionName).toBe(CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL);
});
});

describe('buildOptimisticCancelPaymentReportAction', () => {
it('should set actorAccountID to the provided currentUserAccountID', () => {
const customAccountID = 77;
Expand Down
Loading