diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 54107c6399e0..c559e0ac3217 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -771,6 +771,7 @@ type OptimisticTaskReport = SetRequired< | 'participants' | 'managerID' | 'type' + | 'chatType' | 'parentReportID' | 'policyID' | 'stateNum' @@ -8741,6 +8742,7 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string, exp * @param title - Task title. * @param description - Task description. * @param policyID - PolicyID of the parent report + * @param parentChatType - Chat type of the parent report. */ function buildOptimisticTaskReport( @@ -8751,6 +8753,7 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + parentChatType?: ValueOf, ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { @@ -8771,6 +8774,9 @@ function buildOptimisticTaskReport( participants, managerID: assigneeAccountID, type: CONST.REPORT.TYPE.TASK, + // Only #admins tasks inherit chatType for Concierge routing. + // Task reports are not expense chats, so we do not inherit policyExpenseChat (or other chat types). + ...(parentChatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS && {chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS}), parentReportID, policyID, stateNum: CONST.REPORT.STATE_NUM.OPEN, @@ -11831,9 +11837,10 @@ function prepareOnboardingOnyxData({ const shouldUseFollowupsInsteadOfTasks = shouldPostTasksInAdminsRoom && Permissions.isBetaEnabled(CONST.BETAS.SUGGESTED_FOLLOWUPS, betas ?? allBetas, betaConfiguration); const adminsChatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; const targetChatReport = shouldPostTasksInAdminsRoom - ? (adminsChatReport ?? {reportID: adminsChatReportID, policyID: onboardingPolicyID}) + ? (adminsChatReport ?? {reportID: adminsChatReportID, policyID: onboardingPolicyID, chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS}) : getChatByParticipants([CONST.ACCOUNT_ID.CONCIERGE, deprecatedCurrentUserAccountID ?? CONST.DEFAULT_NUMBER_ID], allReports, false); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; + const targetChatType = targetChatReport?.chatType; if (!targetChatReportID) { Log.warn('Missing reportID for onboarding optimistic data'); @@ -11976,6 +11983,7 @@ function prepareOnboardingOnyxData({ taskDescription, targetChatPolicyID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + targetChatType, ); const emailCreatingAction = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? (allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE) : CONST.EMAIL.CONCIERGE; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index b736d3f47bb8..1ba597a47a5b 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -106,7 +106,16 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { return; } - const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, parentReportID, assigneeAccountID, title, description, policyID); + const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport( + currentUserAccountID, + parentReportID, + assigneeAccountID, + title, + description, + policyID, + CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + parentReport?.chatType, + ); const assigneeChatReportID = assigneeChatReport?.reportID; const taskReportID = optimisticTaskReport.reportID; diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index 17e15e9b5c09..d7397cb8241d 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -308,13 +308,18 @@ describe('actions/Task', () => { global.fetch = getGlobalFetchMock(); // Setup ReportUtils mocks - mockBuildOptimisticTaskReport.mockReturnValue({ - reportID: 'task_report_123', - reportName: mockTitle, - description: mockDescription, - managerID: mockAssigneeAccountID, - type: CONST.REPORT.TYPE.TASK, - parentReportID: mockParentReportID, + mockBuildOptimisticTaskReport.mockImplementation((...args: unknown[]) => { + const parentChatType = args.at(7); + + return { + reportID: 'task_report_123', + reportName: mockTitle, + description: mockDescription, + managerID: mockAssigneeAccountID, + type: CONST.REPORT.TYPE.TASK, + parentReportID: mockParentReportID, + ...(parentChatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS && {chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS}), + }; }); mockBuildOptimisticCreatedReportAction.mockReturnValue({ @@ -425,6 +430,60 @@ describe('actions/Task', () => { ); }); + it('should set optimistic task chatType to policyAdmins for tasks created in admins rooms', async () => { + // Given a task created in a parent report with policyAdmins chatType + createTaskAndNavigate({ + parentReport: { + reportID: mockParentReportID, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, + }, + title: mockTitle, + description: mockDescription, + assigneeEmail: mockAssigneeEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserEmail: mockCurrentUserEmail, + assigneeAccountID: mockAssigneeAccountID, + policyID: mockPolicyID, + isCreatedUsingMarkdown: false, + quickAction: {}, + }); + + await waitForBatchedUpdatesWithAct(); + + // Then the optimistic task report should include policyAdmins chatType + // eslint-disable-next-line rulesdir/no-multiple-api-calls + const [, , onyx] = (API.write as jest.Mock).mock.calls.at(0) as [unknown, unknown, OnyxData]; + const optimisticTaskReportUpdate = onyx.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}task_report_123`); + expect((optimisticTaskReportUpdate?.value as Report | undefined)?.chatType).toBe(CONST.REPORT.CHAT_TYPE.POLICY_ADMINS); + }); + + it('should not set optimistic task chatType for tasks created in policy expense chats', async () => { + // Given a task created in a parent report with policyExpenseChat chatType + createTaskAndNavigate({ + parentReport: { + reportID: mockParentReportID, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + }, + title: mockTitle, + description: mockDescription, + assigneeEmail: mockAssigneeEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserEmail: mockCurrentUserEmail, + assigneeAccountID: mockAssigneeAccountID, + policyID: mockPolicyID, + isCreatedUsingMarkdown: false, + quickAction: {}, + }); + + await waitForBatchedUpdatesWithAct(); + + // Then the optimistic task report should keep chatType unset + // eslint-disable-next-line rulesdir/no-multiple-api-calls + const [, , onyx] = (API.write as jest.Mock).mock.calls.at(0) as [unknown, unknown, OnyxData]; + const optimisticTaskReportUpdate = onyx.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}task_report_123`); + expect((optimisticTaskReportUpdate?.value as Report | undefined)?.chatType).toBeUndefined(); + }); + it('should handle task creation without assignee chat report', async () => { // Given: Task creation without assignee chat report const mockQuickAction = { diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index b1746053c2cc..31c740103087 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -779,6 +779,18 @@ describe('ReportUtils', () => { }); // Without the beta, tasks SHOULD be generated (old behavior) expect(result?.guidedSetupData).toHaveLength(3); + const taskReportIDs = + result?.guidedSetupData.reduce((acc, item) => { + if (item.type === 'task' && typeof item.taskReportID === 'string') { + acc.push(item.taskReportID); + } + return acc; + }, []) ?? []; + expect(taskReportIDs.length).toBeGreaterThan(0); + for (const taskReportID of taskReportIDs) { + const taskReportUpdate = result?.optimisticData.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`); + expect((taskReportUpdate?.value as Report | undefined)?.chatType).toBe(CONST.REPORT.CHAT_TYPE.POLICY_ADMINS); + } }); it('should add guidedSetupData when email has a +', async () => {