Skip to content

Commit

Permalink
Merge pull request #53852 from callstack-internal/fix/attempt-to-fix-…
Browse files Browse the repository at this point in the history
…cycular-dependecies-1
  • Loading branch information
francoisl authored Jan 13, 2025
2 parents d3b7376 + 6f446da commit bfbdb70
Show file tree
Hide file tree
Showing 56 changed files with 1,954 additions and 1,636 deletions.
27 changes: 27 additions & 0 deletions .eslintrc.changed.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ module.exports = {
rules: {
'deprecation/deprecation': 'error',
'rulesdir/no-default-id-values': 'error',
'no-restricted-syntax': [
'error',
{
selector: 'ImportNamespaceSpecifier[parent.source.value=/^@libs/]',
message: 'Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"',
},
{
selector: 'ImportNamespaceSpecifier[parent.source.value=/^@userActions/]',
message: 'Namespace imports from @userActions are not allowed. Use named imports instead. Example: import { action } from "@userActions/module"',
},
],
},
overrides: [
{
Expand All @@ -24,5 +35,21 @@ module.exports = {
'rulesdir/no-default-id-values': 'off',
},
},
{
files: ['**/libs/**/*.{ts,tsx}'],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'ImportNamespaceSpecifier[parent.source.value=/^\\.\\./]',
message: 'Namespace imports are not allowed. Use named imports instead. Example: import { method } from "../libs/module"',
},
{
selector: 'ImportNamespaceSpecifier[parent.source.value=/^\\./]',
message: 'Namespace imports are not allowed. Use named imports instead. Example: import { method } from "./libs/module"',
},
],
},
},
],
};
9 changes: 9 additions & 0 deletions metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ const config = {
// When we run the e2e tests we want files that have the extension e2e.js to be resolved as source files
sourceExts: [...(isE2ETesting ? e2eSourceExts : []), ...defaultSourceExts, 'jsx'],
},
// We are merging the default config from Expo and React Native and expo one is overriding the React Native one so inlineRequires is set to false so we want to set it to true
// for fix cycling dependencies and improve performance of app startup
transformer: {
getTransformOptions: async () => ({
transform: {
inlineRequires: true,
},
}),
},
};

module.exports = wrapWithReanimatedMetroConfig(mergeConfig(defaultConfig, expoConfig, config));
4 changes: 2 additions & 2 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {ValueOf} from 'type-fest';
import type {Video} from './libs/actions/Report';
import type {MileageRate} from './libs/DistanceRequestUtils';
import BankAccount from './libs/models/BankAccount';
import * as Url from './libs/Url';
import {addTrailingForwardSlash} from './libs/Url';
import SCREENS from './SCREENS';
import type PlaidBankAccount from './types/onyx/PlaidBankAccount';

Expand All @@ -18,7 +18,7 @@ const EMPTY_OBJECT = Object.freeze({});

const CLOUDFRONT_DOMAIN = 'cloudfront.net';
const CLOUDFRONT_URL = `https://d2k5nsl2zxldvw.${CLOUDFRONT_DOMAIN}`;
const ACTIVE_EXPENSIFY_URL = Url.addTrailingForwardSlash(Config?.NEW_EXPENSIFY_URL ?? 'https://new.expensify.com');
const ACTIVE_EXPENSIFY_URL = addTrailingForwardSlash(Config?.NEW_EXPENSIFY_URL ?? 'https://new.expensify.com');
const USE_EXPENSIFY_URL = 'https://use.expensify.com';
const EXPENSIFY_URL = 'https://www.expensify.com';
const PLATFORM_OS_MACOS = 'Mac OS';
Expand Down
184 changes: 115 additions & 69 deletions src/components/ReportActionItem/ReportPreview.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {PressableWithFeedback} from '@components/Pressable';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as SearchUIUtils from '@libs/SearchUIUtils';
import {isCorrectSearchUserName} from '@libs/SearchUIUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {SearchPersonalDetails, SearchTransactionAction} from '@src/types/onyx/SearchResults';
Expand Down Expand Up @@ -54,7 +54,7 @@ function ExpenseItemHeaderNarrow({
const theme = useTheme();

// It might happen that we are missing display names for `From` or `To`, we only display arrow icon if both names exist
const shouldDisplayArrowIcon = SearchUIUtils.isCorrectSearchUserName(participantFromDisplayName) && SearchUIUtils.isCorrectSearchUserName(participantToDisplayName);
const shouldDisplayArrowIcon = isCorrectSearchUserName(participantFromDisplayName) && isCorrectSearchUserName(participantToDisplayName);

return (
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween, styles.mb3, styles.gap2, containerStyle]}>
Expand Down
4 changes: 2 additions & 2 deletions src/components/SelectionList/Search/UserInfoCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Avatar from '@components/Avatar';
import Text from '@components/Text';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import * as SearchUIUtils from '@libs/SearchUIUtils';
import {isCorrectSearchUserName} from '@libs/SearchUIUtils';
import CONST from '@src/CONST';
import type {SearchPersonalDetails} from '@src/types/onyx/SearchResults';

Expand All @@ -18,7 +18,7 @@ function UserInfoCell({participant, displayName}: UserInfoCellProps) {
const {isLargeScreenWidth} = useResponsiveLayout();
const avatarURL = participant?.avatar;

if (!SearchUIUtils.isCorrectSearchUserName(displayName)) {
if (!isCorrectSearchUserName(displayName)) {
return null;
}

Expand Down
36 changes: 18 additions & 18 deletions src/libs/API/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {SetRequired} from 'type-fest';
import Log from '@libs/Log';
import * as Middleware from '@libs/Middleware';
import * as SequentialQueue from '@libs/Network/SequentialQueue';
import * as Pusher from '@libs/Pusher/pusher';
import * as Request from '@libs/Request';
import * as PersistedRequests from '@userActions/PersistedRequests';
import {HandleUnusedOptimisticID, Logging, Pagination, Reauthentication, RecheckConnection, SaveResponseInOnyx} from '@libs/Middleware';
import {push as pushToSequentialQueue, waitForIdle as waitForSequentialQueueIdle} from '@libs/Network/SequentialQueue';
import {getPusherSocketID} from '@libs/Pusher/pusher';
import {processWithMiddleware, use} from '@libs/Request';
import {getLength as getPersistedRequestsLength} from '@userActions/PersistedRequests';
import CONST from '@src/CONST';
import type OnyxRequest from '@src/types/onyx/Request';
import type {PaginatedRequest, PaginationConfig, RequestConflictResolver} from '@src/types/onyx/Request';
Expand All @@ -18,22 +18,22 @@ import type {ApiCommand, ApiRequestCommandParameters, ApiRequestType, CommandOfT
// e.g. an error thrown in Logging or Reauthenticate logic will be caught by the next middleware or the SequentialQueue which retries failing requests.

// Logging - Logs request details and errors.
Request.use(Middleware.Logging);
use(Logging);

// RecheckConnection - Sets a timer for a request that will "recheck" if we are connected to the internet if time runs out. Also triggers the connection recheck when we encounter any error.
Request.use(Middleware.RecheckConnection);
use(RecheckConnection);

// Reauthentication - Handles jsonCode 407 which indicates an expired authToken. We need to reauthenticate and get a new authToken with our stored credentials.
Request.use(Middleware.Reauthentication);
use(Reauthentication);

// If an optimistic ID is not used by the server, this will update the remaining serialized requests using that optimistic ID to use the correct ID instead.
Request.use(Middleware.HandleUnusedOptimisticID);
use(HandleUnusedOptimisticID);

Request.use(Middleware.Pagination);
use(Pagination);

// SaveResponseInOnyx - Merges either the successData or failureData (or finallyData, if included in place of the former two values) into Onyx depending on if the call was successful or not. This needs to be the LAST middleware we use, don't add any
// middlewares after this, because the SequentialQueue depends on the result of this middleware to pause the queue (if needed) to bring the app to an up-to-date state.
Request.use(Middleware.SaveResponseInOnyx);
use(SaveResponseInOnyx);

type OnyxData = {
optimisticData?: OnyxUpdate[];
Expand Down Expand Up @@ -69,7 +69,7 @@ function prepareRequest<TCommand extends ApiCommand>(

// We send the pusherSocketID with all write requests so that the api can include it in push events to prevent Pusher from sending the events to the requesting client. The push event
// is sent back to the requesting client in the response data instead, which prevents a replay effect in the UI. See https://github.com/Expensify/App/issues/12775.
pusherSocketID: isWriteRequest ? Pusher.getPusherSocketID() : undefined,
pusherSocketID: isWriteRequest ? getPusherSocketID() : undefined,
};

// Assemble all request metadata (used by middlewares, and for persisted requests stored in Onyx)
Expand All @@ -95,18 +95,18 @@ function prepareRequest<TCommand extends ApiCommand>(
function processRequest(request: OnyxRequest, type: ApiRequestType): Promise<void | Response> {
// Write commands can be saved and retried, so push it to the SequentialQueue
if (type === CONST.API_REQUEST_TYPE.WRITE) {
SequentialQueue.push(request);
pushToSequentialQueue(request);
return Promise.resolve();
}

// Read requests are processed right away, but don't return the response to the caller
if (type === CONST.API_REQUEST_TYPE.READ) {
Request.processWithMiddleware(request);
processWithMiddleware(request);
return Promise.resolve();
}

// Requests with side effects process right away, and return the response to the caller
return Request.processWithMiddleware(request);
return processWithMiddleware(request);
}

/**
Expand Down Expand Up @@ -172,10 +172,10 @@ function makeRequestWithSideEffects<TCommand extends SideEffectRequestCommand>(
* write requests that use the same Onyx keys and haven't responded yet.
*/
function waitForWrites<TCommand extends ReadCommand>(command: TCommand) {
if (PersistedRequests.getLength() > 0) {
Log.info(`[API] '${command}' is waiting on ${PersistedRequests.getLength()} write commands`);
if (getPersistedRequestsLength() > 0) {
Log.info(`[API] '${command}' is waiting on ${getPersistedRequestsLength()} write commands`);
}
return SequentialQueue.waitForIdle();
return waitForSequentialQueueIdle();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/libs/API/parameters/ResolveDuplicatesParams.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type ResolveDuplicatesParams = {
/** The ID of the transaction that we want to keep */
transactionID: string;
transactionID: string | undefined;

/** The list of other duplicated transactions */
transactionIDList: string[];
Expand Down
39 changes: 20 additions & 19 deletions src/libs/DistanceRequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import type {LastSelectedDistanceRates, OnyxInputOrEntry, Report, Transaction} f
import type {Unit} from '@src/types/onyx/Policy';
import type Policy from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import * as CurrencyUtils from './CurrencyUtils';
import * as PolicyUtils from './PolicyUtils';
import * as ReportUtils from './ReportUtils';
import * as TransactionUtils from './TransactionUtils';
import {getCurrencySymbol} from './CurrencyUtils';
import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getPersonalPolicy, getPolicy, getUnitRateValue} from './PolicyUtils';
import {isPolicyExpenseChat} from './ReportUtils';
import {getCurrency, getRateID, isCustomUnitRateIDForP2P} from './TransactionUtils';

type MileageRate = {
customUnitRateID?: string;
Expand Down Expand Up @@ -48,7 +48,7 @@ function getMileageRates(policy: OnyxInputOrEntry<Policy>, includeDisabledRates
return mileageRates;
}

const distanceUnit = PolicyUtils.getDistanceRateCustomUnit(policy);
const distanceUnit = getDistanceRateCustomUnit(policy);
if (!distanceUnit?.rates) {
return mileageRates;
}
Expand Down Expand Up @@ -90,7 +90,7 @@ function getDefaultMileageRate(policy: OnyxInputOrEntry<Policy>): MileageRate |
return undefined;
}

const distanceUnit = PolicyUtils.getDistanceRateCustomUnit(policy);
const distanceUnit = getDistanceRateCustomUnit(policy);
if (!distanceUnit?.rates || !distanceUnit.attributes) {
return;
}
Expand Down Expand Up @@ -162,9 +162,9 @@ function getRateForDisplay(
}

const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer');
const formattedRate = PolicyUtils.getUnitRateValue(toLocaleDigit, {rate}, useShortFormUnit);
const formattedRate = getUnitRateValue(toLocaleDigit, {rate}, useShortFormUnit);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const currencySymbol = CurrencyUtils.getCurrencySymbol(currency) || `${currency} `;
const currencySymbol = getCurrencySymbol(currency) || `${currency} `;

return `${currencySymbol}${formattedRate} / ${useShortFormUnit ? unit : singularDistanceUnit}`;
}
Expand Down Expand Up @@ -250,7 +250,7 @@ function getRateForP2P(currency: string, transaction: OnyxEntry<Transaction>): M
ensureRateDefined(mileageRate.rate);

// Ensure the rate is updated when the currency changes, otherwise use the stored rate
const rate = TransactionUtils.getCurrency(transaction) === currency ? transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate : mileageRate.rate;
const rate = getCurrency(transaction) === currency ? transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate : mileageRate.rate;
return {
...mileageRate,
currency: currencyWithExistingRate,
Expand Down Expand Up @@ -297,13 +297,13 @@ function getCustomUnitRateID(reportID?: string) {
}
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`];
const policy = PolicyUtils.getPolicy(report?.policyID ?? parentReport?.policyID);
const policy = getPolicy(report?.policyID ?? parentReport?.policyID);

if (isEmptyObject(policy)) {
return customUnitRateID;
}

if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) {
if (isPolicyExpenseChat(report) || isPolicyExpenseChat(parentReport)) {
const distanceUnit = Object.values(policy.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
const lastSelectedDistanceRateID = lastSelectedDistanceRates?.[policy.id];
const lastSelectedDistanceRate = lastSelectedDistanceRateID ? distanceUnit?.rates[lastSelectedDistanceRateID] : undefined;
Expand All @@ -325,15 +325,15 @@ function getCustomUnitRateID(reportID?: string) {
* Get taxable amount from a specific distance rate, taking into consideration the tax claimable amount configured for the distance rate
*/
function getTaxableAmount(policy: OnyxEntry<Policy>, customUnitRateID: string, distance: number) {
const distanceUnit = PolicyUtils.getDistanceRateCustomUnit(policy);
const customUnitRate = PolicyUtils.getDistanceRateCustomUnitRate(policy, customUnitRateID);
if (!distanceUnit || !distanceUnit?.customUnitID || !customUnitRate) {
const distanceUnit = getDistanceRateCustomUnit(policy);
const customUnitRate = getDistanceRateCustomUnitRate(policy, customUnitRateID);
if (!distanceUnit?.customUnitID || !customUnitRate) {
return 0;
}
const unit = distanceUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES;
const rate = customUnitRate?.rate ?? 0;
const rate = customUnitRate?.rate ?? CONST.DEFAULT_NUMBER_ID;
const amount = getDistanceRequestAmount(distance, unit, rate);
const taxClaimablePercentage = customUnitRate.attributes?.taxClaimablePercentage ?? 0;
const taxClaimablePercentage = customUnitRate.attributes?.taxClaimablePercentage ?? CONST.DEFAULT_NUMBER_ID;
return amount * taxClaimablePercentage;
}

Expand All @@ -360,10 +360,11 @@ function getRate({
if (isEmptyObject(mileageRates) && policyDraft) {
mileageRates = getMileageRates(policyDraft, true, transaction?.comment?.customUnit?.customUnitRateID);
}
const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD;
const policyCurrency = policy?.outputCurrency ?? getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD;
const defaultMileageRate = getDefaultMileageRate(policy);
const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '';
const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(policyCurrency, transaction) : mileageRates?.[customUnitRateID] ?? defaultMileageRate;
const customUnitRateID = getRateID(transaction);
const customMileageRate = customUnitRateID ? mileageRates?.[customUnitRateID] : defaultMileageRate;
const mileageRate = isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(policyCurrency, transaction) : customMileageRate;
const unit = getDistanceUnit(useTransactionDistanceUnit ? transaction : undefined, mileageRate);
return {
...mileageRate,
Expand Down
12 changes: 6 additions & 6 deletions src/libs/HttpUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {RequestType} from '@src/types/onyx/Request';
import type Response from '@src/types/onyx/Response';
import * as NetworkActions from './actions/Network';
import * as UpdateRequired from './actions/UpdateRequired';
import {setTimeSkew} from './actions/Network';
import {alertUser} from './actions/UpdateRequired';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from './API/types';
import * as ApiUtils from './ApiUtils';
import {getCommandURL} from './ApiUtils';
import HttpsError from './Errors/HttpsError';
import getPlatform from './getPlatform';

Expand Down Expand Up @@ -77,7 +77,7 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form
const endTime = new Date().valueOf();
const latency = (endTime - startTime) / 2;
const skew = serverTime - startTime + latency;
NetworkActions.setTimeSkew(dateHeaderValue ? skew : 0);
setTimeSkew(dateHeaderValue ? skew : 0);
}
return response;
})
Expand Down Expand Up @@ -148,7 +148,7 @@ function processHTTPRequest(url: string, method: RequestType = 'get', body: Form
}
if (response.jsonCode === CONST.JSON_CODE.UPDATE_REQUIRED) {
// Trigger a modal and disable the app as the user needs to upgrade to the latest minimum version to continue
UpdateRequired.alertUser();
alertUser();
}
return response as Promise<Response>;
});
Expand All @@ -172,7 +172,7 @@ function xhr(command: string, data: Record<string, unknown>, type: RequestType =
formData.append(key, value as string | Blob);
});

const url = ApiUtils.getCommandURL({shouldUseSecure, command});
const url = getCommandURL({shouldUseSecure, command});

const abortSignalController = data.canCancel ? abortControllerMap.get(command as AbortCommand) ?? abortControllerMap.get(ABORT_COMMANDS.All) : undefined;
return processHTTPRequest(url, type, formData, abortSignalController?.signal);
Expand Down
4 changes: 2 additions & 2 deletions src/libs/Log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import pkg from '../../package.json';
import {addLog, flushAllLogsOnAppLaunch} from './actions/Console';
import {shouldAttachLog} from './Console';
import getPlatform from './getPlatform';
import * as Network from './Network';
import {post} from './Network';
import requireParameters from './requireParameters';

let timeout: NodeJS.Timeout;
Expand Down Expand Up @@ -40,7 +40,7 @@ function LogCommand(parameters: LogCommandParameters): Promise<{requestID: strin

// Note: We are forcing Log to run since it requires no authToken and should only be queued when we are offline.
// Non-cancellable request: during logout, when requests are cancelled, we don't want to cancel any remaining logs
return Network.post(commandName, {...parameters, forceNetworkRequest: true, canCancel: false}) as Promise<{requestID: string}>;
return post(commandName, {...parameters, forceNetworkRequest: true, canCancel: false}) as Promise<{requestID: string}>;
}

// eslint-disable-next-line
Expand Down
Loading

0 comments on commit bfbdb70

Please sign in to comment.