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
3 changes: 2 additions & 1 deletion src/components/SettlementButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,12 @@ function SettlementButton({
// The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here.
// eslint-disable-next-line rulesdir/no-default-id-values
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`, {canBeMissing: true});
const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true});
const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport?.reportID}`, {canBeMissing: true});

const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector, canBeMissing: true});
const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, accountID);
const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID) : false;
const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID, conciergeReportID) : false;
const policyIDKey = reportBelongsToWorkspace ? policyID : (iouReport?.policyID ?? CONST.POLICY.ID_FAKE);
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? '');
Expand Down
5 changes: 3 additions & 2 deletions src/components/SidePanel/HelpComponents/HelpContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function HelpContent({closeSidePanel}: HelpContentProps) {
});

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${params?.reportID || String(CONST.DEFAULT_NUMBER_ID)}`, {canBeMissing: true});
const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true});
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`, {canBeMissing: true});
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`, {canBeMissing: true});

Expand Down Expand Up @@ -89,7 +90,7 @@ function HelpContent({closeSidePanel}: HelpContentProps) {

const cleanedPath = path.replaceAll('?', '');
const expenseType = getExpenseType(transaction);
const reportType = getHelpPaneReportType(report);
const reportType = getHelpPaneReportType(report, conciergeReportID);

if (expenseType && reportType !== CONST.REPORT.HELP_TYPE.EXPENSE_REPORT) {
return cleanedPath.replaceAll(':reportID', `:${CONST.REPORT.HELP_TYPE.EXPENSE}/:${expenseType}`);
Expand All @@ -100,7 +101,7 @@ function HelpContent({closeSidePanel}: HelpContentProps) {
}

return cleanedPath;
}, [routeName, transaction, report]);
}, [routeName, transaction, report, conciergeReportID]);

const wasPreviousNarrowScreen = useRef(!isExtraLargeScreenWidth);
useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/usePaymentOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ function usePaymentOptions({
// The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here.
// eslint-disable-next-line rulesdir/no-default-id-values
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID || CONST.DEFAULT_NUMBER_ID}`, {canBeMissing: true});
const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true});
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? '');
const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, accountID);
const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID) : false;
const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID, conciergeReportID) : false;
const policyIDKey = reportBelongsToWorkspace ? policyID : CONST.POLICY.ID_FAKE;
const lastPaymentMethodSelector = useCallback(
(paymentMethod: OnyxEntry<LastPaymentMethod>) => {
Expand Down
18 changes: 9 additions & 9 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1001,11 +1001,11 @@ getEnvironment().then((env) => {
// Example case: when we need to get a report name of a thread which is dependent on a report action message.
const parsedReportActionMessageCache: Record<string, string> = {};

let conciergeReportID: OnyxEntry<string>;
let conciergeReportIDOnyxConnect: OnyxEntry<string>;
Onyx.connectWithoutView({
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => {
conciergeReportID = value;
conciergeReportIDOnyxConnect = value;
},
});

Expand Down Expand Up @@ -1882,8 +1882,8 @@ function getReportNotificationPreference(report: OnyxEntry<Report>): ValueOf<typ
/**
* Only returns true if this is our main 1:1 DM report with Concierge.
*/
function isConciergeChatReport(report: OnyxInputOrEntry<Report>): boolean {
return !!report && report?.reportID === conciergeReportID;
function isConciergeChatReport(report: OnyxInputOrEntry<Report>, conciergeReportID?: string): boolean {
return !!report && report?.reportID === (conciergeReportID ?? conciergeReportIDOnyxConnect);
}

function findSelfDMReportID(): string | undefined {
Expand All @@ -1909,9 +1909,9 @@ function isPolicyRelatedReport(report: OnyxEntry<Report>, policyID?: string) {
* Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM.
* In this case report and workspace members must be compared to determine whether the report belongs to the workspace.
*/
function doesReportBelongToWorkspace(report: OnyxEntry<Report>, policyMemberAccountIDs: number[], policyID?: string) {
function doesReportBelongToWorkspace(report: OnyxEntry<Report>, policyMemberAccountIDs: number[], policyID: string | undefined, conciergeReportID: string) {
return (
isConciergeChatReport(report) ||
isConciergeChatReport(report, conciergeReportID) ||
(report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : isPolicyRelatedReport(report, policyID))
);
}
Expand Down Expand Up @@ -1974,7 +1974,7 @@ function shouldEnableNegative(report: OnyxEntry<Report>, policy?: OnyxEntry<Poli
* Given an array of reports, return them filtered by a policyID and policyMemberAccountIDs.
*/
function filterReportsByPolicyIDAndMemberAccountIDs(reports: Array<OnyxEntry<Report>>, policyMemberAccountIDs: number[] = [], policyID?: string) {
return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyMemberAccountIDs, policyID));
return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyMemberAccountIDs, policyID, conciergeReportIDOnyxConnect ?? ''));
}

/**
Expand Down Expand Up @@ -2574,12 +2574,12 @@ function isMoneyRequestReport(reportOrID: OnyxInputOrEntry<Report> | string, rep
/**
* Determines the Help Panel report type based on the given report.
*/
function getHelpPaneReportType(report: OnyxEntry<Report>): ValueOf<typeof CONST.REPORT.HELP_TYPE> | undefined {
function getHelpPaneReportType(report: OnyxEntry<Report>, conciergeReportID: string): ValueOf<typeof CONST.REPORT.HELP_TYPE> | undefined {
if (!report) {
return undefined;
}

if (isConciergeChatReport(report)) {
if (isConciergeChatReport(report, conciergeReportID)) {
return CONST.REPORT.HELP_TYPE.CHAT_CONCIERGE;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ function SuggestionMention({
suggestionValuesRef.current = suggestionValues;

const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true});

const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const isMentionSuggestionsMenuVisible = !!suggestionValues.suggestedMentions.length && suggestionValues.shouldShowSuggestionMenu;
Expand All @@ -99,7 +100,7 @@ function SuggestionMention({
);
const weightedPersonalDetails: PersonalDetailsList | SuggestionPersonalDetailsList | undefined = useMemo(() => {
const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, currentUserPersonalDetails.accountID);
if (!isGroupChat(currentReport) && !doesReportBelongToWorkspace(currentReport, policyEmployeeAccountIDs, policyID)) {
if (!isGroupChat(currentReport) && !doesReportBelongToWorkspace(currentReport, policyEmployeeAccountIDs, policyID, conciergeReportID)) {
return personalDetails;
}
return lodashMapValues(personalDetails, (detail) =>
Expand All @@ -110,7 +111,7 @@ function SuggestionMention({
}
: null,
);
}, [policyID, policy, currentReport, personalDetails, getPersonalDetailsWeight, currentUserPersonalDetails.accountID]);
}, [policyID, policy, currentReport, personalDetails, getPersonalDetailsWeight, currentUserPersonalDetails.accountID, conciergeReportID]);

const [highlightedMentionIndex, setHighlightedMentionIndex] = useArrowKeyFocusManager({
isActive: isMentionSuggestionsMenuVisible,
Expand Down
220 changes: 220 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
canSeeDefaultRoom,
canUserPerformWriteAction,
createDraftTransactionAndNavigateToParticipantSelector,
doesReportBelongToWorkspace,
excludeParticipantsForDisplay,
findLastAccessedReport,
getAllReportActionsErrorsAndReportActionThatRequiresAttention,
Expand All @@ -65,6 +66,7 @@ import {
getDisplayNameForParticipant,
getDisplayNamesWithTooltips,
getHarvestOriginalReportID,
getHelpPaneReportType,
getIconsForParticipants,
getIndicatedMissingPaymentMethod,
getIOUReportActionDisplayMessage,
Expand Down Expand Up @@ -11061,6 +11063,224 @@ describe('ReportUtils', () => {
});
});

describe('doesReportBelongToWorkspace', () => {
const policyID = 'test-policy-123';
const conciergeReportID = 'concierge-report-456';

beforeEach(async () => {
await Onyx.clear();
await waitForBatchedUpdates();
});

it('should return true for concierge chat report when conciergeReportID matches', () => {
const conciergeReport: Report = {
reportID: conciergeReportID,
type: CONST.REPORT.TYPE.CHAT,
};

const result = doesReportBelongToWorkspace(conciergeReport, [], policyID, conciergeReportID);
expect(result).toBe(true);
});

it('should return false for concierge chat report when conciergeReportID does not match', () => {
const conciergeReport: Report = {
reportID: 'different-report-id',
type: CONST.REPORT.TYPE.CHAT,
policyID: CONST.POLICY.ID_FAKE,
};

const result = doesReportBelongToWorkspace(conciergeReport, [], policyID, conciergeReportID);
expect(result).toBe(false);
});

it('should return true for policy related report with matching policyID', () => {
const policyReport: Report = {
reportID: 'policy-report-123',
type: CONST.REPORT.TYPE.CHAT,
policyID,
};

const result = doesReportBelongToWorkspace(policyReport, [], policyID, conciergeReportID);
expect(result).toBe(true);
});

it('should return true for DM report with participant in workspace', () => {
const dmReport: Report = {
reportID: 'dm-report-123',
type: CONST.REPORT.TYPE.CHAT,
policyID: CONST.POLICY.ID_FAKE,
participants: {
1: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
2: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
},
};
const policyMemberAccountIDs = [1, 2];

const result = doesReportBelongToWorkspace(dmReport, policyMemberAccountIDs, policyID, conciergeReportID);
expect(result).toBe(true);
});

it('should return false for DM report with no participants in workspace', () => {
const dmReport: Report = {
reportID: 'dm-report-123',
type: CONST.REPORT.TYPE.CHAT,
policyID: CONST.POLICY.ID_FAKE,
participants: {
3: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
4: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
},
};
const policyMemberAccountIDs = [1, 2];

const result = doesReportBelongToWorkspace(dmReport, policyMemberAccountIDs, policyID, conciergeReportID);
expect(result).toBe(false);
});

it('should return false for report with no policyID and no matching participants', () => {
const report: Report = {
reportID: 'report-123',
type: CONST.REPORT.TYPE.CHAT,
participants: {
5: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
},
};

const result = doesReportBelongToWorkspace(report, [1, 2], policyID, conciergeReportID);
expect(result).toBe(false);
});

it('should return false for invoice report with different policyID in invoiceReceiver', () => {
const invoiceReport: Report = {
reportID: 'invoice-report-123',
type: CONST.REPORT.TYPE.INVOICE,
policyID: 'different-policy',
invoiceReceiver: {
policyID: 'another-different-policy',
type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS,
},
};

const result = doesReportBelongToWorkspace(invoiceReport, [], policyID, conciergeReportID);
expect(result).toBe(false);
});

it('should return true for invoice report with matching policyID in invoiceReceiver', () => {
const invoiceReport: Report = {
reportID: 'invoice-report-123',
type: CONST.REPORT.TYPE.INVOICE,
policyID: 'different-policy',
invoiceReceiver: {
policyID,
type: CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS,
},
};

const result = doesReportBelongToWorkspace(invoiceReport, [], policyID, conciergeReportID);
expect(result).toBe(true);
});
});

describe('getHelpPaneReportType', () => {
const conciergeReportID = 'concierge-report-456';

it('should return undefined for undefined report', () => {
const result = getHelpPaneReportType(undefined, conciergeReportID);
expect(result).toBeUndefined();
});

it('should return CHAT_CONCIERGE for concierge chat report', () => {
const conciergeReport: Report = {
reportID: conciergeReportID,
type: CONST.REPORT.TYPE.CHAT,
};

const result = getHelpPaneReportType(conciergeReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.HELP_TYPE.CHAT_CONCIERGE);
});

it('should return chatType for report with chatType', () => {
const groupChatReport: Report = {
reportID: 'group-chat-123',
type: CONST.REPORT.TYPE.CHAT,
chatType: CONST.REPORT.CHAT_TYPE.GROUP,
};

const result = getHelpPaneReportType(groupChatReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.CHAT_TYPE.GROUP);
});

it('should return EXPENSE_REPORT for expense report type', () => {
const expenseReport: Report = {
reportID: 'expense-report-123',
type: CONST.REPORT.TYPE.EXPENSE,
};

const result = getHelpPaneReportType(expenseReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.HELP_TYPE.EXPENSE_REPORT);
});

it('should return CHAT for chat report type without chatType', () => {
const chatReport: Report = {
reportID: 'chat-report-123',
type: CONST.REPORT.TYPE.CHAT,
};

const result = getHelpPaneReportType(chatReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.HELP_TYPE.CHAT);
});

it('should return IOU for IOU report type', () => {
const iouReport: Report = {
reportID: 'iou-report-123',
type: CONST.REPORT.TYPE.IOU,
};

const result = getHelpPaneReportType(iouReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.HELP_TYPE.IOU);
});

it('should return INVOICE for invoice report type', () => {
const invoiceReport: Report = {
reportID: 'invoice-report-123',
type: CONST.REPORT.TYPE.INVOICE,
};

const result = getHelpPaneReportType(invoiceReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.HELP_TYPE.INVOICE);
});

it('should return TASK for task report type', () => {
const taskReport: Report = {
reportID: 'task-report-123',
type: CONST.REPORT.TYPE.TASK,
};

const result = getHelpPaneReportType(taskReport, conciergeReportID);
expect(result).toBe(CONST.REPORT.HELP_TYPE.TASK);
});

it('should return undefined for unknown report type', () => {
const unknownReport: Report = {
reportID: 'unknown-report-123',
type: 'unknown' as Report['type'],
};

const result = getHelpPaneReportType(unknownReport, conciergeReportID);
expect(result).toBeUndefined();
});

it('should not return CHAT_CONCIERGE when conciergeReportID does not match', () => {
const chatReport: Report = {
reportID: 'regular-chat-123',
type: CONST.REPORT.TYPE.CHAT,
};

const result = getHelpPaneReportType(chatReport, conciergeReportID);
// This report has type CHAT but is not the concierge report
expect(result).toBe(CONST.REPORT.HELP_TYPE.CHAT);
});
});

describe('createDraftTransactionAndNavigateToParticipantSelector', () => {
describe('when action is CATEGORIZE', () => {
beforeEach(async () => {
Expand Down
Loading