From f16ab1db30c74b15134139411a018950826d137e Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Date: Thu, 5 Mar 2026 15:29:03 +0530 Subject: [PATCH 1/3] Align optimistic task chatType inheritance with backend behavior --- src/libs/ReportUtils.ts | 8 +++++ src/libs/actions/Task.ts | 11 +++++- tests/actions/TaskTest.ts | 73 +++++++++++++++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c7f157d65287..66955b668cf2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -736,6 +736,7 @@ type OptimisticTaskReport = SetRequired< | 'participants' | 'managerID' | 'type' + | 'chatType' | 'parentReportID' | 'policyID' | 'stateNum' @@ -8457,6 +8458,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( @@ -8467,6 +8469,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]: { @@ -8487,6 +8490,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, @@ -11404,6 +11410,7 @@ function prepareOnboardingOnyxData({ ? (adminsChatReport ?? {reportID: adminsChatReportID, policyID: onboardingPolicyID}) : getChatByParticipants([CONST.ACCOUNT_ID.CONCIERGE, currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID], allReports, false); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; + const targetChatType = targetChatReport && typeof targetChatReport === 'object' && 'chatType' in targetChatReport ? targetChatReport.chatType : undefined; if (!targetChatReportID) { Log.warn('Missing reportID for onboarding optimistic data'); @@ -11542,6 +11549,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 451286240055..fb87c3662bed 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -105,7 +105,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 97e69a53ccce..fefa0816a1b5 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -301,13 +301,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({ @@ -418,6 +423,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 = { From b94401b2114ac938165e49d520c0710ccef52ab6 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Date: Tue, 17 Mar 2026 12:01:10 +0530 Subject: [PATCH 2/3] Preserve policyAdmins chatType in onboarding task fallback Ensure onboarding tasks inherit policyAdmins when admins chat data is fallback-only, and strengthen unit coverage by asserting chatType on each generated onboarding task report. --- src/libs/ReportUtils.ts | 2 +- tests/unit/ReportUtilsTest.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7003e6b95269..d1fd30095d3a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11614,7 +11614,7 @@ 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, currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID], allReports, false); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; const targetChatType = targetChatReport && typeof targetChatReport === 'object' && 'chatType' in targetChatReport ? targetChatReport.chatType : undefined; diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 5227afd2a31b..b40568e74711 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -597,6 +597,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 () => { From 4f59d6bc118583e04528212488cb1c069e78bf3e Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Date: Mon, 30 Mar 2026 21:46:40 +0530 Subject: [PATCH 3/3] Simplify onboarding target chatType access. Use optional chaining to read chatType from targetChatReport, avoiding a verbose object/type guard. --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1fc9e9497de1..a546f8435b4e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11742,7 +11742,7 @@ function prepareOnboardingOnyxData({ ? (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 && typeof targetChatReport === 'object' && 'chatType' in targetChatReport ? targetChatReport.chatType : undefined; + const targetChatType = targetChatReport?.chatType; if (!targetChatReportID) { Log.warn('Missing reportID for onboarding optimistic data');