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
9 changes: 7 additions & 2 deletions src/libs/actions/Policy/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
};

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 46 in src/libs/actions/Policy/Member.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.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => (allReportActions = actions),
Expand Down Expand Up @@ -821,6 +821,8 @@
currentUserAccountID: number | undefined,
approverEmail?: string,
policyExpenseChatNotificationPreference?: NotificationPreference,
// TODO: Remove optional (?) once all callers are updated in follow-up PRs of https://github.com/Expensify/App/issues/66578
reportActionsList?: OnyxCollection<ReportActions>,
) {
const policyID = policy.id;
const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin));
Expand All @@ -841,8 +843,7 @@
const announceRoomChat = optimisticAnnounceChat.announceChatData;

// create onyx data for policy expense chats for each new member
// TODO: Update to include reportActionsList later (https://github.com/Expensify/App/issues/66578)
const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, undefined, undefined, policyExpenseChatNotificationPreference);
const membersChats = createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, reportActionsList, undefined, policyExpenseChatNotificationPreference);

const optimisticMembersState: OnyxCollectionInputValue<PolicyEmployee> = {};
const successMembersState: OnyxCollectionInputValue<PolicyEmployee> = {};
Expand Down Expand Up @@ -954,6 +955,8 @@
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
currentUserAccountID: number | undefined,
approverEmail?: string,
// TODO: Remove optional (?) once all callers are updated in follow-up PRs of https://github.com/Expensify/App/issues/66578
reportActionsList?: OnyxCollection<ReportActions>,
) {
if (!policy?.id) {
Log.warn('addMembersToWorkspace: Policy ID is undefined');
Expand All @@ -967,6 +970,8 @@
formatPhoneNumber,
currentUserAccountID,
approverEmail,
undefined,
reportActionsList,
);

const params: AddMembersToWorkspaceParams = {
Expand Down
1 change: 0 additions & 1 deletion src/libs/actions/Policy/Policy.ts
Comment thread
DylanDylann marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@
};

const deprecatedAllPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 258 in src/libs/actions/Policy/Policy.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,
callback: (val, key) => {
if (!key) {
Expand All @@ -271,7 +271,7 @@
});

let deprecatedAllReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 274 in src/libs/actions/Policy/Policy.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.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -281,7 +281,7 @@

let deprecatedSessionEmail = '';
let deprecatedSessionAccountID = 0;
Onyx.connect({

Check warning on line 284 in src/libs/actions/Policy/Policy.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.SESSION,
callback: (val) => {
deprecatedSessionEmail = val?.email ?? '';
Expand All @@ -290,7 +290,7 @@
});

let deprecatedAllPersonalDetails: OnyxEntry<PersonalDetailsList>;
Onyx.connect({

Check warning on line 293 in src/libs/actions/Policy/Policy.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.PERSONAL_DETAILS_LIST,
callback: (val) => (deprecatedAllPersonalDetails = val),
});
Expand Down Expand Up @@ -2940,7 +2940,6 @@
}

if (adminParticipant?.login) {
// TODO: Update to include reportActionsList later (https://github.com/Expensify/App/issues/66578)
const employeeWorkspaceChat = createPolicyExpenseChats(
policyID,
{[adminParticipant.login]: adminParticipant.accountID ?? CONST.DEFAULT_NUMBER_ID},
Expand Down
4 changes: 4 additions & 0 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@
// map of reportID to all reportActions for that report
const allReportActions: OnyxCollection<ReportActions> = {};

Onyx.connect({

Check warning on line 390 in src/libs/actions/Report/index.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.REPORT_ACTIONS,
callback: (actions, key) => {
if (!key || !actions) {
Expand All @@ -399,7 +399,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 402 in src/libs/actions/Report/index.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.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -408,7 +408,7 @@
});

let allPersonalDetails: OnyxEntry<PersonalDetailsList> = {};
Onyx.connect({

Check warning on line 411 in src/libs/actions/Report/index.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.PERSONAL_DETAILS_LIST,
callback: (value) => {
allPersonalDetails = value ?? {};
Expand All @@ -423,7 +423,7 @@
});

let onboarding: OnyxEntry<Onboarding>;
Onyx.connect({

Check warning on line 426 in src/libs/actions/Report/index.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.NVP_ONBOARDING,
callback: (val) => {
if (Array.isArray(val)) {
Expand Down Expand Up @@ -7197,6 +7197,7 @@
formatPhoneNumber,
isReportLastVisibleArchived,
reportNextStep,
reportActionsList,
}: {
report: Report;
parentReport: OnyxEntry<Report>;
Expand All @@ -7210,6 +7211,8 @@
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'];
isReportLastVisibleArchived: boolean | undefined;
reportNextStep: OnyxEntry<ReportNextStepDeprecated>;
// TODO: Remove optional (?) once all callers are updated in follow-up PRs of https://github.com/Expensify/App/issues/66578
reportActionsList?: OnyxCollection<ReportActions>;
}) {
if (!report.reportID || !policy?.id || report.policyID === policy.id || !isExpenseReport(report) || !report.ownerAccountID) {
return;
Expand All @@ -7235,6 +7238,7 @@
currentUserAccountID,
undefined,
CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
reportActionsList,
);
const optimisticPolicyExpenseChatReportID = membersChats.reportCreationData[submitterEmail].reportID;
const optimisticPolicyExpenseChatCreatedReportActionID = membersChats.reportCreationData[submitterEmail].reportActionID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMembe
import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections';
import type {Section} from '@components/SelectionList/SelectionListWithSections/types';
import Text from '@components/Text';
import useAllPolicyExpenseChatReportActions from '@hooks/useAllPolicyExpenseChatReportActions';
import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -66,6 +67,7 @@ function BaseOnboardingWorkspaceInvite({shouldUseNativeStyles}: BaseOnboardingWo
const {isBetaEnabled} = usePermissions();
const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const archivedReportsIdSet = useArchivedReportsIdSet();
const filteredReportActions = useAllPolicyExpenseChatReportActions();

const ineligibleInvitees = getIneligibleInvitees(policy?.employeeList);
const excludedUsers: Record<string, boolean> = {};
Expand Down Expand Up @@ -182,6 +184,8 @@ function BaseOnboardingWorkspaceInvite({shouldUseNativeStyles}: BaseOnboardingWo
CONST.POLICY.ROLE.USER,
formatPhoneNumber,
currentUserPersonalDetails.accountID,
undefined,
filteredReportActions,
);
completeOnboarding(true);
};
Expand Down
1 change: 1 addition & 0 deletions src/pages/ReportChangeWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ function ReportChangeWorkspacePage({report, route}: ReportChangeWorkspacePagePro
formatPhoneNumber,
isReportLastVisibleArchived,
reportNextStep,
reportActionsList: filteredReportActions,
});
} else {
changeReportPolicy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {AnimatedTextInputRef} from '@components/RNTextInput';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import useAllPolicyExpenseChatReportActions from '@hooks/useAllPolicyExpenseChatReportActions';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -68,6 +69,7 @@ function WorkspaceInviteMessageComponent({
const {translate, formatPhoneNumber} = useLocalize();
const [formData, formDataResult] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM_DRAFT);
const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const filteredReportActions = useAllPolicyExpenseChatReportActions();

const [welcomeNote, setWelcomeNote] = useState<string>();

Expand Down Expand Up @@ -158,6 +160,7 @@ function WorkspaceInviteMessageComponent({
formatPhoneNumber,
currentUserPersonalDetails?.accountID,
shouldShowApproverRow ? validatedApprover : undefined,
filteredReportActions,
);
setWorkspaceInviteMessageDraft(policyID, welcomeNote ?? null);
clearDraftValues(ONYXKEYS.FORMS.WORKSPACE_INVITE_MESSAGE_FORM);
Expand Down
60 changes: 60 additions & 0 deletions tests/actions/PolicyMemberTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,66 @@ describe('actions/PolicyMember', () => {
});
expect(isWorkspaceChatArchived && isExpenseReportArchived).toBe(false);
});

it('should unarchive existing expense report via the explicit reportActionsList param (not the deprecated onyx fallback)', async () => {
// Given an archived workspace expense chat + expense report AND no REPORT_ACTIONS seeded in Onyx
const policyID = '1';
const workspaceReportID = '1';
const expenseReportID = '2';
const userAccountID = 1236;
const userEmail = 'user@example.com';
const policy = createRandomPolicy(Number(policyID));

await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${workspaceReportID}`, {
...createRandomReport(Number(workspaceReportID), CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT),
policyID,
ownerAccountID: userAccountID,
});
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${workspaceReportID}`, {
private_isArchived: DateUtils.getDBTime(),
});
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${expenseReportID}`, {
private_isArchived: DateUtils.getDBTime(),
});

// Build the REPORT_PREVIEW action outside Onyx and pass it explicitly
const expenseAction: ReportAction = {
...createRandomReportAction(0),
actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW,
childReportID: expenseReportID,
};
const reportActionsList = {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${workspaceReportID}`]: {
[expenseAction.reportActionID]: expenseAction,
},
};

// When adding the user with the explicit reportActionsList argument
Member.addMembersToWorkspace(
{[userEmail]: userAccountID},
'Welcome',
policy,
[],
CONST.POLICY.ROLE.USER,
TestHelper.formatPhoneNumber,
currentUserAccountID,
undefined,
reportActionsList,
);

await waitForBatchedUpdates();

const isExpenseReportArchived = await new Promise<boolean>((resolve) => {
const connection = Onyx.connect({
key: `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${expenseReportID}`,
callback: (nvp) => {
Onyx.disconnect(connection);
resolve(!!nvp?.private_isArchived);
},
});
});
expect(isExpenseReportArchived).toBe(false);
});
});

describe('removeMembers', () => {
Expand Down
118 changes: 118 additions & 0 deletions tests/actions/PolicyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,124 @@ describe('actions/Policy', () => {

expect(GoogleTagManager.publishEvent).not.toHaveBeenCalled();
});

it('should not include memberData when adminParticipant is not provided', async () => {
await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID});
await waitForBatchedUpdates();

const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve());
const policyID = Policy.generatePolicyID();

Policy.createWorkspace({
policyOwnerEmail: ESH_EMAIL,
makeMeAdmin: true,
policyName: WORKSPACE_NAME,
policyID,
introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM},
currentUserAccountIDParam: ESH_ACCOUNT_ID,
currentUserEmailParam: ESH_EMAIL,
isSelfTourViewed: false,
betas: undefined,
hasActiveAdminPolicies: false,
});
await waitForBatchedUpdates();

const apiCallArgs = apiWriteSpy.mock.calls.find((call) => call.at(0) === WRITE_COMMANDS.CREATE_WORKSPACE);
expect(apiCallArgs).toBeDefined();
const params = apiCallArgs?.[1] as {memberData?: string};
expect(params.memberData).toBeUndefined();

apiWriteSpy.mockRestore();
});

it('should generate optimistic expense chat report when adminParticipant is provided', async () => {
await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID});
await waitForBatchedUpdates();

const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve());
const policyID = Policy.generatePolicyID();
const adminEmail = 'admin@example.com';
const adminAccountID = 555;

Policy.createWorkspace({
policyOwnerEmail: ESH_EMAIL,
makeMeAdmin: true,
policyName: WORKSPACE_NAME,
policyID,
introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM},
currentUserAccountIDParam: ESH_ACCOUNT_ID,
currentUserEmailParam: ESH_EMAIL,
isSelfTourViewed: false,
betas: undefined,
hasActiveAdminPolicies: false,
adminParticipant: {login: adminEmail, accountID: adminAccountID},
});
await waitForBatchedUpdates();

// Verify memberData contains the workspaceChatReportID (created by createPolicyExpenseChats)
const apiCallArgs = apiWriteSpy.mock.calls.find((call) => call.at(0) === WRITE_COMMANDS.CREATE_WORKSPACE);
const params = apiCallArgs?.[1] as {memberData?: string};
const memberData = JSON.parse(params.memberData ?? '{}') as {workspaceChatReportID: string; workspaceChatCreatedReportActionID: string};
expect(memberData.workspaceChatReportID).toBeTruthy();
expect(memberData.workspaceChatCreatedReportActionID).toBeTruthy();

// Verify that optimistic data includes the expense chat report
const onyxData = apiCallArgs?.[2] as {optimisticData?: Array<{key: string; value: unknown}>};
const expenseChatOptimisticData = onyxData.optimisticData?.find((data) => data.key === `${ONYXKEYS.COLLECTION.REPORT}${memberData.workspaceChatReportID}`);
expect(expenseChatOptimisticData).toBeTruthy();

apiWriteSpy.mockRestore();
});

it('should always create a new expense chat for adminParticipant since policyID is freshly generated', async () => {
await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID});
await waitForBatchedUpdates();

const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve());
const adminEmail = 'admin@example.com';
const adminAccountID = 555;

// Create two workspaces with the same adminParticipant — each should get a unique expense chat
Policy.createWorkspace({
policyOwnerEmail: ESH_EMAIL,
makeMeAdmin: true,
policyName: 'Workspace 1',
introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM},
currentUserAccountIDParam: ESH_ACCOUNT_ID,
currentUserEmailParam: ESH_EMAIL,
isSelfTourViewed: false,
betas: undefined,
hasActiveAdminPolicies: false,
adminParticipant: {login: adminEmail, accountID: adminAccountID},
});
await waitForBatchedUpdates();

Policy.createWorkspace({
policyOwnerEmail: ESH_EMAIL,
makeMeAdmin: true,
policyName: 'Workspace 2',
introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM},
currentUserAccountIDParam: ESH_ACCOUNT_ID,
currentUserEmailParam: ESH_EMAIL,
isSelfTourViewed: false,
betas: undefined,
hasActiveAdminPolicies: false,
adminParticipant: {login: adminEmail, accountID: adminAccountID},
});
await waitForBatchedUpdates();

// Both calls should produce different workspaceChatReportIDs
const calls = apiWriteSpy.mock.calls.filter((call) => call.at(0) === WRITE_COMMANDS.CREATE_WORKSPACE);
expect(calls).toHaveLength(2);

const memberData1 = JSON.parse((calls.at(0)?.at(1) as {memberData: string}).memberData) as {workspaceChatReportID: string};
const memberData2 = JSON.parse((calls.at(1)?.at(1) as {memberData: string}).memberData) as {workspaceChatReportID: string};
expect(memberData1.workspaceChatReportID).toBeTruthy();
expect(memberData2.workspaceChatReportID).toBeTruthy();
expect(memberData1.workspaceChatReportID).not.toBe(memberData2.workspaceChatReportID);

apiWriteSpy.mockRestore();
});
});

describe('updateWorkspaceAvatar', () => {
Expand Down
Loading