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
41 changes: 2 additions & 39 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import {
shouldShowYear as shouldShowYearUtil,
} from '@libs/SearchUIUtils';
import {cancelSpan, endSpan, startSpan} from '@libs/telemetry/activeSpans';
import {getOriginalTransactionWithSplitInfo, isOnHold, isTransactionPendingDelete, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
import {getOriginalTransactionWithSplitInfo, isOnHold, isTransactionPendingDelete} from '@libs/TransactionUtils';
import Navigation, {navigationRef} from '@navigation/Navigation';
import type {SearchFullscreenNavigatorParamList} from '@navigation/types';
import EmptySearchView from '@pages/Search/EmptySearchView';
Expand All @@ -71,7 +71,6 @@ import {columnsSelector} from '@src/selectors/AdvancedSearchFiltersForm';
import {isActionLoadingSetSelector} from '@src/selectors/ReportMetaData';
import type {OutstandingReportsByPolicyIDDerivedValue, Transaction} from '@src/types/onyx';
import type SearchResults from '@src/types/onyx/SearchResults';
import type {TransactionViolation} from '@src/types/onyx/TransactionViolation';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import arraysEqual from '@src/utils/arraysEqual';
import {useSearchContext} from './SearchContext';
Expand Down Expand Up @@ -251,42 +250,6 @@ function Search({

const isExpenseReportType = type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT;

// Filter violations based on user visibility
const filteredViolations = useMemo(() => {
if (!violations || !searchResults?.data) {
return violations;
}

const filtered: Record<string, TransactionViolation[]> = {};

const transactionKeys = Object.keys(searchResults.data).filter((key) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION));

for (const key of transactionKeys) {
const transaction = searchResults.data[key as keyof typeof searchResults.data] as Transaction;
if (!transaction || typeof transaction !== 'object' || !('transactionID' in transaction) || !('reportID' in transaction)) {
continue;
}

const report = searchResults.data[`${ONYXKEYS.COLLECTION.REPORT}${transaction.reportID}`];
const policy = searchResults.data[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`];

if (report && policy) {
const transactionViolations = violations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`];
if (transactionViolations) {
const filteredTransactionViolations = mergeProhibitedViolations(
transactionViolations.filter((violation) => shouldShowViolation(report, policy, violation.name, email ?? '', true, transaction)),
);

if (filteredTransactionViolations.length > 0) {
filtered[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`] = filteredTransactionViolations;
}
}
}
}

return filtered;
}, [violations, searchResults, email]);

const archivedReportsIdSet = useArchivedReportsIdSet();

const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
Expand Down Expand Up @@ -1088,7 +1051,7 @@ function Search({
}
queryJSON={queryJSON}
columns={columnsToShow}
violations={filteredViolations}
violations={violations}
Comment on lines 1052 to +1054

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reapply violation visibility filtering before passing

In grouped search views (e.g. TransactionGroupListExpanded/TransactionItemRow), the list pulls violations via getTransactionViolations, which only filters dismissed items and does not apply shouldShowViolation (see TransactionUtils); previously Search pre-filtered the collection by visibility. Passing the raw violations here means users who are not submitters or policy admins can now see violations that are explicitly hidden (e.g., RTER/auto‑reported rejected/receipt-not-smart-scanned) in search results. To avoid exposing restricted violations, this needs a visibility filter here (or inside the downstream helpers) before handing them to the list.

Useful? React with 👍 / 👎.

onLayout={onLayout}
isMobileSelectionModeEnabled={isMobileSelectionModeEnabled}
shouldAnimate={type === CONST.SEARCH.DATA_TYPES.EXPENSE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import useThemeStyles from '@hooks/useThemeStyles';
import type {TransactionPreviewData} from '@libs/actions/Search';
import {handleActionButtonPress as handleActionButtonPressUtil} from '@libs/actions/Search';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {isViolationDismissed, shouldShowViolation} from '@libs/TransactionUtils';
import {isViolationDismissed, mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {isActionLoadingSelector} from '@src/selectors/ReportMetaData';
import type {Policy, Report, ReportAction, ReportActions} from '@src/types/onyx';
import type {TransactionViolation} from '@src/types/onyx/TransactionViolation';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import UserInfoAndActionButtonRow from './UserInfoAndActionButtonRow';

function TransactionListItem<TItem extends ListItem>({
Expand Down Expand Up @@ -69,6 +70,7 @@ function TransactionListItem<TItem extends ListItem>({
const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true});

const [parentReport] = originalUseOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(transactionItem.reportID)}`, {canBeMissing: true});
const [parentPolicy] = originalUseOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(transactionItem.policyID)}`, {canBeMissing: true});
const [transactionThreadReport] = originalUseOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionItem?.reportAction?.childReportID}`, {canBeMissing: true});
const [transaction] = originalUseOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionItem.transactionID)}`, {canBeMissing: true});
const parentReportActionSelector = useCallback(
Expand Down Expand Up @@ -120,13 +122,20 @@ function TransactionListItem<TItem extends ListItem>({
transactionItem.shouldShowYearExported,
]);

// Use parentReport/parentPolicy as fallbacks when snapshotReport/snapshotPolicy are empty
// to fix offline issues where newly created reports aren't in the search snapshot yet
const reportForViolations = isEmptyObject(snapshotReport) ? parentReport : snapshotReport;
const policyForViolations = isEmptyObject(snapshotPolicy) ? parentPolicy : snapshotPolicy;

const transactionViolations = useMemo(() => {
return (violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionItem.transactionID}`] ?? []).filter(
(violation: TransactionViolation) =>
!isViolationDismissed(transactionItem, violation, currentUserDetails.email ?? '', currentUserDetails.accountID, snapshotReport, snapshotPolicy) &&
shouldShowViolation(snapshotReport, snapshotPolicy, violation.name, currentUserDetails.email ?? '', false, transactionItem),
return mergeProhibitedViolations(
(violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionItem.transactionID}`] ?? []).filter(
(violation: TransactionViolation) =>
!isViolationDismissed(transactionItem, violation, currentUserDetails.email ?? '', currentUserDetails.accountID, reportForViolations, policyForViolations) &&
shouldShowViolation(reportForViolations, policyForViolations, violation.name, currentUserDetails.email ?? '', false, transactionItem),
),
);
}, [snapshotPolicy, snapshotReport, transactionItem, violations, currentUserDetails.email, currentUserDetails.accountID]);
}, [policyForViolations, reportForViolations, transactionItem, violations, currentUserDetails.email, currentUserDetails.accountID]);

const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);

Expand Down
Loading