Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run e2e tests on PR #35907 #37143

Closed
wants to merge 15 commits into from
Closed
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1460,6 +1460,8 @@ const CONST = {
ALPHABETIC_AND_LATIN_CHARS: /^[\p{Script=Latin} ]*$/u,
NON_ALPHABETIC_AND_NON_LATIN_CHARS: /[^\p{Script=Latin}]/gu,
ACCENT_LATIN_CHARS: /[\u00C0-\u017F]/g,
INVALID_DISPLAY_NAME_LHN: /[^\p{L}\p{N}\u00C0-\u017F\s-]/gu,
INVALID_DISPLAY_NAME_ONLY_LHN: /^[^\p{L}\p{N}\u00C0-\u017F\s-]$/gu,
POSITIVE_INTEGER: /^\d+$/,
PO_BOX: /\b[P|p]?(OST|ost)?\.?\s*[O|o|0]?(ffice|FFICE)?\.?\s*[B|b][O|o|0]?[X|x]?\.?\s+[#]?(\d+)\b/,
ANY_VALUE: /^.+$/,
Expand Down
4 changes: 4 additions & 0 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function LHNOptionsList({
draftComments = {},
transactionViolations = {},
onFirstItemRendered = () => {},
reportIDsWithErrors = {},
}: LHNOptionsListProps) {
const styles = useThemeStyles();
const {canUseViolations} = usePermissions();
Expand Down Expand Up @@ -63,6 +64,7 @@ function LHNOptionsList({
const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? '';
const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions);
const lastReportAction = sortedReportActions[0];
const reportErrors = reportIDsWithErrors[reportID] ?? {};

// Get the transaction for the last report action
let lastReportActionTransactionID = '';
Expand Down Expand Up @@ -91,6 +93,7 @@ function LHNOptionsList({
transactionViolations={transactionViolations}
canUseViolations={canUseViolations}
onLayout={onLayoutItem}
reportErrors={reportErrors}
/>
);
},
Expand All @@ -109,6 +112,7 @@ function LHNOptionsList({
transactionViolations,
canUseViolations,
onLayoutItem,
reportIDsWithErrors,
],
);

Expand Down
4 changes: 3 additions & 1 deletion src/components/LHNOptionsList/OptionRowLHNData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function OptionRowLHNData({
lastReportActionTransaction = {},
transactionViolations,
canUseViolations,
reportErrors,
...propsToForward
}: OptionRowLHNDataProps) {
const reportID = propsToForward.reportID;
Expand All @@ -40,11 +41,11 @@ function OptionRowLHNData({
// Note: ideally we'd have this as a dependent selector in onyx!
const item = SidebarUtils.getOptionData({
report: fullReport,
reportActions,
personalDetails,
preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT,
policy,
parentReportAction,
reportErrors,
hasViolations: !!hasViolations,
});
if (deepEqual(item, optionItemRef.current)) {
Expand All @@ -69,6 +70,7 @@ function OptionRowLHNData({
transactionViolations,
canUseViolations,
receiptTransactions,
reportErrors,
]);

useEffect(() => {
Expand Down
6 changes: 6 additions & 0 deletions src/components/LHNOptionsList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'
import type CONST from '@src/CONST';
import type {OptionData} from '@src/libs/ReportUtils';
import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type {EmptyObject} from '@src/types/utils/EmptyObject';

type OptionMode = ValueOf<typeof CONST.OPTION_MODE>;
Expand Down Expand Up @@ -58,6 +59,8 @@ type CustomLHNOptionsListProps = {

/** Callback to fire when the list is laid out */
onFirstItemRendered: () => void;

reportIDsWithErrors: Record<string, OnyxCommon.Errors>;
};

type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps;
Expand Down Expand Up @@ -113,6 +116,9 @@ type OptionRowLHNDataProps = {

/** Callback to execute when the OptionList lays out */
onLayout?: (event: LayoutChangeEvent) => void;

/** The report errors */
reportErrors: OnyxCommon.Errors | undefined;
};

type OptionRowLHNProps = {
Expand Down
31 changes: 23 additions & 8 deletions src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx';
import type Beta from '@src/types/onyx/Beta';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type Policy from '@src/types/onyx/Policy';
import type Report from '@src/types/onyx/Report';
import type {ReportActions} from '@src/types/onyx/ReportAction';
Expand Down Expand Up @@ -59,6 +60,13 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 {
return 0;
}

function filterDisplayName(displayName: string): string {
if (CONST.REGEX.INVALID_DISPLAY_NAME_ONLY_LHN.test(displayName)) {
return displayName;
}
return displayName.replace(CONST.REGEX.INVALID_DISPLAY_NAME_LHN, '');
}

/**
* @returns An array of reportIDs sorted in the proper order
*/
Expand All @@ -68,22 +76,27 @@ function getOrderedReportIDs(
betas: Beta[],
policies: Record<string, Policy>,
priorityMode: ValueOf<typeof CONST.PRIORITY_MODE>,
allReportActions: OnyxCollection<ReportAction[]>,
allReportActions: OnyxCollection<ReportActions>,
transactionViolations: OnyxCollection<TransactionViolation[]>,
currentPolicyID = '',
policyMemberAccountIDs: number[] = [],
reportIDsWithErrors: Record<string, OnyxCommon.Errors> = {},
): string[] {
const isInGSDMode = priorityMode === CONST.PRIORITY_MODE.GSD;
const isInDefaultMode = !isInGSDMode;
const allReportsDictValues = Object.values(allReports);

const reportIDsWithViolations = new Set<string>();

// Filter out all the reports that shouldn't be displayed
let reportsToDisplay = allReportsDictValues.filter((report) => {
const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`;
const parentReportActions = allReportActions?.[parentReportActionsKey];
const parentReportAction = parentReportActions?.find((action) => action && report && action?.reportActionID === report?.parentReportActionID);
const parentReportAction = allReportActions?.[parentReportActionsKey]?.[report.parentReportActionID ?? ''];
const doesReportHaveViolations =
betas.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction);
if (doesReportHaveViolations) {
reportIDsWithViolations.add(report.reportID);
}
return ReportUtils.shouldReportBeInOptionList({
report,
currentReportId: currentReportId ?? '',
Expand Down Expand Up @@ -126,11 +139,13 @@ function getOrderedReportIDs(
// However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add
// the reportDisplayName property to the report object directly.
// eslint-disable-next-line no-param-reassign
report.displayName = ReportUtils.getReportName(report);
report.displayName = filterDisplayName(ReportUtils.getReportName(report));

const hasRBR = report.reportID in reportIDsWithErrors || reportIDsWithViolations.has(report.reportID);

const isPinned = report.isPinned ?? false;
const reportAction = ReportActionsUtils.getReportAction(report.parentReportID ?? '', report.parentReportActionID ?? '');
if (isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) {
if (isPinned || hasRBR || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) {
pinnedAndGBRReports.push(report);
} else if (report.hasDraft) {
draftReports.push(report);
Expand Down Expand Up @@ -169,19 +184,19 @@ function getOrderedReportIDs(
*/
function getOptionData({
report,
reportActions,
personalDetails,
preferredLocale,
policy,
parentReportAction,
reportErrors,
hasViolations,
}: {
report: OnyxEntry<Report>;
reportActions: OnyxEntry<ReportActions>;
personalDetails: OnyxEntry<PersonalDetailsList>;
preferredLocale: DeepValueOf<typeof CONST.LOCALES>;
policy: OnyxEntry<Policy> | undefined;
parentReportAction: OnyxEntry<ReportAction> | undefined;
reportErrors: OnyxCommon.Errors | undefined;
hasViolations: boolean;
}): ReportUtils.OptionData | undefined {
// When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for
Expand All @@ -194,7 +209,7 @@ function getOptionData({
const result: ReportUtils.OptionData = {
text: '',
alternateText: null,
allReportErrors: OptionsListUtils.getAllReportErrors(report, reportActions),
allReportErrors: reportErrors,
brickRoadIndicator: null,
tooltipText: null,
subtitle: null,
Expand Down
3 changes: 2 additions & 1 deletion src/pages/home/sidebar/SidebarLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const propTypes = {
isActiveReport: PropTypes.func.isRequired,
};

function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}) {
function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy, reportIDsWithErrors}) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const modal = useRef({});
Expand Down Expand Up @@ -154,6 +154,7 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority
shouldDisableFocusOptions={isSmallScreenWidth}
optionMode={viewMode}
onFirstItemRendered={App.setSidebarLoaded}
reportIDsWithErrors={reportIDsWithErrors}
/>
{isLoading && optionListItems.length === 0 && (
<View style={[StyleSheet.absoluteFillObject, styles.appBG]}>
Expand Down
77 changes: 58 additions & 19 deletions src/pages/home/sidebar/SidebarLinksData.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {deepEqual} from 'fast-equals';
import lodashGet from 'lodash/get';
import lodashMap from 'lodash/map';
import lodashMapValues from 'lodash/mapValues';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import {View} from 'react-native';
Expand All @@ -16,9 +16,11 @@ import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import SidebarUtils from '@libs/SidebarUtils';
import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
import reportPropTypes from '@pages/reportPropTypes';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
Expand All @@ -35,20 +37,7 @@ const propTypes = {
/** All report actions for all reports */

/** Object of report actions for this report */
allReportActions: PropTypes.objectOf(
PropTypes.arrayOf(
PropTypes.shape({
error: PropTypes.string,
message: PropTypes.arrayOf(
PropTypes.shape({
moderationDecision: PropTypes.shape({
decision: PropTypes.string,
}),
}),
),
}),
),
),
allReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),

/** Whether the reports are loading. When false it means they are ready to be used. */
isLoadingApp: PropTypes.bool,
Expand Down Expand Up @@ -139,6 +128,23 @@ function SidebarLinksData({
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => Policy.openWorkspace(activeWorkspaceID, policyMemberAccountIDs), [activeWorkspaceID]);

const reportIDsWithErrors = useMemo(() => {
const reportKeys = _.keys(chatReports);
return _.reduce(
reportKeys,
(errorsMap, reportKey) => {
const report = chatReports[reportKey];
const allReportsActions = allReportActions[reportKey.replace(ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.COLLECTION.REPORT_ACTIONS)];
const errors = OptionsListUtils.getAllReportErrors(report, allReportsActions) || {};
if (_.size(errors) === 0) {
return errorsMap;
}
return {...errorsMap, [reportKey.replace(ONYXKEYS.COLLECTION.REPORT, '')]: errors};
},
{},
);
}, [allReportActions, chatReports]);

const reportIDsRef = useRef(null);
const isLoading = isLoadingApp;
const optionListItems = useMemo(() => {
Expand All @@ -152,6 +158,7 @@ function SidebarLinksData({
transactionViolations,
activeWorkspaceID,
policyMemberAccountIDs,
reportIDsWithErrors,
);

if (deepEqual(reportIDsRef.current, reportIDs)) {
Expand All @@ -165,7 +172,20 @@ function SidebarLinksData({
reportIDsRef.current = reportIDs;
}
return reportIDsRef.current || [];
}, [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, isLoading, network.isOffline, prevPriorityMode]);
}, [
chatReports,
betas,
policies,
priorityMode,
allReportActions,
transactionViolations,
activeWorkspaceID,
policyMemberAccountIDs,
reportIDsWithErrors,
isLoading,
network.isOffline,
prevPriorityMode,
]);

// We need to make sure the current report is in the list of reports, but we do not want
// to have to re-generate the list every time the currentReportID changes. To do that
Expand All @@ -184,10 +204,23 @@ function SidebarLinksData({
transactionViolations,
activeWorkspaceID,
policyMemberAccountIDs,
reportIDsWithErrors,
);
}
return optionListItems;
}, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs]);
}, [
currentReportID,
optionListItems,
chatReports,
betas,
policies,
priorityMode,
allReportActions,
transactionViolations,
activeWorkspaceID,
policyMemberAccountIDs,
reportIDsWithErrors,
]);

const currentReportIDRef = useRef(currentReportID);
currentReportIDRef.current = currentReportID;
Expand All @@ -209,6 +242,7 @@ function SidebarLinksData({
isLoading={isLoading}
optionListItems={optionListItemsWithCurrentReport}
activeWorkspaceID={activeWorkspaceID}
reportIDsWithErrors={reportIDsWithErrors}
/>
</View>
);
Expand All @@ -232,6 +266,7 @@ const chatReportSelector = (report) =>
isPinned: report.isPinned,
isHidden: report.isHidden,
notificationPreference: report.notificationPreference,
errors: report.errors,
errorFields: {
addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom,
},
Expand All @@ -253,6 +288,9 @@ const chatReportSelector = (report) =>
reportName: report.reportName,
policyName: report.policyName,
oldPolicyName: report.oldPolicyName,
isPolicyExpenseChat: report.isPolicyExpenseChat,
isOwnPolicyExpenseChat: report.isOwnPolicyExpenseChat,
isCancelledIOU: report.isCancelledIOU,
// Other less obvious properites considered for sorting:
ownerAccountID: report.ownerAccountID,
currency: report.currency,
Expand All @@ -270,15 +308,16 @@ const chatReportSelector = (report) =>
*/
const reportActionsSelector = (reportActions) =>
reportActions &&
lodashMap(reportActions, (reportAction) => {
const {reportActionID, parentReportActionID, actionName, errors = []} = reportAction;
lodashMapValues(reportActions, (reportAction) => {
const {reportActionID, parentReportActionID, actionName, originalMessage, errors = []} = reportAction;
const decision = lodashGet(reportAction, 'message[0].moderationDecision.decision');

return {
reportActionID,
parentReportActionID,
actionName,
errors,
originalMessage,
message: [
{
moderationDecision: {decision},
Expand Down
Loading
Loading