From 71d5b83558ca43652eb841897007bc5e391ddb7c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 29 Nov 2023 12:43:16 +0100 Subject: [PATCH 01/15] Migrate src/libs/getModalState.js --- src/libs/{getModalState.js => getModalState.ts} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename src/libs/{getModalState.js => getModalState.ts} (57%) diff --git a/src/libs/getModalState.js b/src/libs/getModalState.ts similarity index 57% rename from src/libs/getModalState.js rename to src/libs/getModalState.ts index 30ff8499809a..47aa0c406b29 100644 --- a/src/libs/getModalState.js +++ b/src/libs/getModalState.ts @@ -1,12 +1,13 @@ -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; +import Modal from '@src/types/onyx/Modal'; -let modalState = {}; +let modalState: OnyxEntry = {}; Onyx.connect({ key: ONYXKEYS.MODAL, - callback: (val) => { - modalState = val; + callback: (value) => { + modalState = value; }, }); @@ -14,8 +15,7 @@ Onyx.connect({ * Returns the modal state from onyx. * Note: You should use the HOCs/hooks to get onyx data, instead of using this directly. * A valid use case to use this is if the value is only needed once for an initial value. - * @returns {Object} */ -export default function getModalState() { +export default function getModalState(): OnyxEntry { return modalState; } From 863114e553d487eb4dad45e58c3a40d816b0ba37 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 29 Nov 2023 12:55:29 +0100 Subject: [PATCH 02/15] Migrate src/libs/NextStepUtils.js --- src/libs/{NextStepUtils.js => NextStepUtils.ts} | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) rename src/libs/{NextStepUtils.js => NextStepUtils.ts} (68%) diff --git a/src/libs/NextStepUtils.js b/src/libs/NextStepUtils.ts similarity index 68% rename from src/libs/NextStepUtils.js rename to src/libs/NextStepUtils.ts index 303f9b2a9925..1d99da633a4e 100644 --- a/src/libs/NextStepUtils.js +++ b/src/libs/NextStepUtils.ts @@ -1,11 +1,15 @@ import Str from 'expensify-common/lib/str'; -import _ from 'underscore'; -function parseMessage(messageToParse) { +type Message = { + text: string; + type?: string; +}; + +function parseMessage(messages: Message[]) { let nextStepHTML = ''; - _.each(messageToParse, (part) => { - const tagType = part.type || 'span'; + messages?.forEach((part) => { + const tagType = part.type ?? 'span'; nextStepHTML += `<${tagType}>${Str.safeEscape(part.text)}`; }); From 194c1e14690d67ede84df568c613117153bc2eac Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 29 Nov 2023 13:00:55 +0100 Subject: [PATCH 03/15] Migrate src/libs/HeaderUtils.js --- src/libs/{HeaderUtils.js => HeaderUtils.ts} | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) rename src/libs/{HeaderUtils.js => HeaderUtils.ts} (75%) diff --git a/src/libs/HeaderUtils.js b/src/libs/HeaderUtils.ts similarity index 75% rename from src/libs/HeaderUtils.js rename to src/libs/HeaderUtils.ts index 2edca95ecf74..39dc0bc706e3 100644 --- a/src/libs/HeaderUtils.js +++ b/src/libs/HeaderUtils.ts @@ -1,13 +1,17 @@ +import {SvgProps} from 'react-native-svg'; import * as Expensicons from '@components/Icon/Expensicons'; +import OnyxReport from '@src/types/onyx/Report'; import * as Report from './actions/Report'; import * as Session from './actions/Session'; import * as Localize from './Localize'; -/** - * @param {Object} report - * @returns {Object} pin/unpin object - */ -function getPinMenuItem(report) { +type MenuItem = { + icon: string | React.FC; + text: string; + onSelected: () => void; +}; + +function getPinMenuItem(report: OnyxReport): MenuItem { if (!report.isPinned) { return { icon: Expensicons.Pin, From 6d2e5009b0bb263b9c3350820d646b6e64373bb8 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 29 Nov 2023 13:56:18 +0100 Subject: [PATCH 04/15] Migrate src/libs/DistanceRequestUtils.ts --- src/libs/DistanceRequestUtils.js | 112 ---------------------------- src/libs/DistanceRequestUtils.ts | 121 +++++++++++++++++++++++++++++++ src/libs/HeaderUtils.ts | 7 +- src/types/onyx/Policy.ts | 21 +++++- 4 files changed, 145 insertions(+), 116 deletions(-) delete mode 100644 src/libs/DistanceRequestUtils.js create mode 100644 src/libs/DistanceRequestUtils.ts diff --git a/src/libs/DistanceRequestUtils.js b/src/libs/DistanceRequestUtils.js deleted file mode 100644 index 0f994cc54f93..000000000000 --- a/src/libs/DistanceRequestUtils.js +++ /dev/null @@ -1,112 +0,0 @@ -import _ from 'underscore'; -import CONST from '@src/CONST'; -import * as CurrencyUtils from './CurrencyUtils'; -import * as PolicyUtils from './PolicyUtils'; - -/** - * Retrieves the default mileage rate based on a given policy. - * - * @param {Object} policy - The policy from which to extract the default mileage rate. - * @param {Object} [policy.customUnits] - Custom units defined in the policy. - * @param {Object[]} [policy.customUnits.rates] - Rates used in the policy. - * @param {Object} [policy.customUnits.attributes] - attributes on a custom unit - * @param {'mi' | 'km'} [policy.customUnits.attributes.unit] - unit of measurement for the distance - * - * @returns {Object|null} An object containing the rate and unit for the default mileage or null if not found. - * @returns {Number} .rate - The default rate for the mileage. - * @returns {String} .currency - The currency associated with the rate. - * @returns {String} .unit - The unit of measurement for the distance. - */ -const getDefaultMileageRate = (policy) => { - if (!policy || !policy.customUnits) { - return null; - } - - const distanceUnit = _.find(_.values(policy.customUnits), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - if (!distanceUnit) { - return null; - } - - const distanceRate = _.find(_.values(distanceUnit.rates), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - if (!distanceRate) { - return null; - } - - return { - rate: distanceRate.rate, - currency: distanceRate.currency, - unit: distanceUnit.attributes.unit, - }; -}; - -/** - * Converts a given distance in meters to the specified unit (kilometers or miles). - * - * @param {Number} distanceInMeters - The distance in meters to be converted. - * @param {'mi' | 'km'} unit - The desired unit of conversion, either 'km' for kilometers or 'mi' for miles. - * - * @returns {Number} The converted distance in the specified unit. - */ -function convertDistanceUnit(distanceInMeters, unit) { - const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters - const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter - - switch (unit) { - case CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS: - return distanceInMeters * METERS_TO_KM; - case CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES: - return distanceInMeters * METERS_TO_MILES; - default: - throw new Error('Unsupported unit. Supported units are "mi" or "km".'); - } -} - -/** - * - * @param {Number} distanceInMeters Distance traveled - * @param {'mi' | 'km'} unit Unit that should be used to display the distance - * @returns {String} The distance in requested units, rounded to 2 decimals - */ -const getRoundedDistanceInUnits = (distanceInMeters, unit) => { - const convertedDistance = convertDistanceUnit(distanceInMeters, unit); - return convertedDistance.toFixed(2); -}; - -/** - * - * @param {boolean} hasRoute Whether the route exists for the distance request - * @param {Number} distanceInMeters Distance traveled - * @param {'mi' | 'km'} unit Unit that should be used to display the distance - * @param {Number} rate Expensable amount allowed per unit - * @param {String} currency The currency associated with the rate - * @param {Function} translate Translate function - * @param {Function} toLocaleDigit Function to convert to localized digit - * @returns {String} A string that describes the distance traveled and the rate used for expense calculation - */ -const getDistanceMerchant = (hasRoute, distanceInMeters, unit, rate, currency, translate, toLocaleDigit) => { - const distanceInUnits = hasRoute ? getRoundedDistanceInUnits(distanceInMeters, unit) : translate('common.tbd'); - - const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); - const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); - const unitString = distanceInUnits === 1 ? singularDistanceUnit : distanceUnit; - const ratePerUnit = rate ? PolicyUtils.getUnitRateValue({rate}, toLocaleDigit) : translate('common.tbd'); - const currencySymbol = rate ? CurrencyUtils.getCurrencySymbol(currency) || `${currency} ` : ''; - - return `${distanceInUnits} ${unitString} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; -}; - -/** - * Calculates the request amount based on distance, unit, and rate. - * - * @param {Number} distance - The distance traveled in meters - * @param {'mi' | 'km'} unit - The unit of measurement for the distance - * @param {Number} rate - Rate used for calculating the request amount - * @returns {Number} The computed request amount (rounded) in "cents". - */ -const getDistanceRequestAmount = (distance, unit, rate) => { - const convertedDistance = convertDistanceUnit(distance, unit); - const roundedDistance = convertedDistance.toFixed(2); - return Math.round(roundedDistance * rate); -}; - -export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount}; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts new file mode 100644 index 000000000000..ad0b814f1e5c --- /dev/null +++ b/src/libs/DistanceRequestUtils.ts @@ -0,0 +1,121 @@ +import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import CONST from '@src/CONST'; +import Policy, {Unit} from '@src/types/onyx/Policy'; +import * as CurrencyUtils from './CurrencyUtils'; +import * as PolicyUtils from './PolicyUtils'; + +type DefaultMileageRate = { + rate: number; + currency: string; + unit: Unit; +}; + +/** + * Retrieves the default mileage rate based on a given policy. + * + * @param policy - The policy from which to extract the default mileage rate. + * + * @returns An object containing the rate and unit for the default mileage or null if not found. + * @returns [rate] - The default rate for the mileage. + * @returns [currency] - The currency associated with the rate. + * @returns [unit] - The unit of measurement for the distance. + */ +function getDefaultMileageRate(policy: Policy): DefaultMileageRate | null { + if (!policy?.customUnits) { + return null; + } + + const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceUnit) { + return null; + } + + const distanceRate = Object.values(distanceUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + if (!distanceRate) { + return null; + } + + return { + rate: distanceRate.rate, + currency: distanceRate.currency, + unit: distanceUnit.attributes.unit, + }; +} + +/** + * Converts a given distance in meters to the specified unit (kilometers or miles). + * + * @param distanceInMeters - The distance in meters to be converted. + * @param unit - The desired unit of conversion, either 'km' for kilometers or 'mi' for miles. + * + * @returns The converted distance in the specified unit. + */ +function convertDistanceUnit(distanceInMeters: number, unit: Unit): number { + const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters + const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter + + switch (unit) { + case CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS: + return distanceInMeters * METERS_TO_KM; + case CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES: + return distanceInMeters * METERS_TO_MILES; + default: + throw new Error('Unsupported unit. Supported units are "mi" or "km".'); + } +} + +/** + * @param distanceInMeters Distance traveled + * @param unit Unit that should be used to display the distance + * @returns The distance in requested units, rounded to 2 decimals + */ +function getRoundedDistanceInUnits(distanceInMeters: number, unit: Unit): string { + const convertedDistance = convertDistanceUnit(distanceInMeters, unit); + return convertedDistance.toFixed(2); +} + +/** + * @param hasRoute Whether the route exists for the distance request + * @param distanceInMeters Distance traveled + * @param unit Unit that should be used to display the distance + * @param rate Expensable amount allowed per unit + * @param currency The currency associated with the rate + * @param translate Translate function + * @param toLocaleDigit Function to convert to localized digit + * @returns A string that describes the distance traveled and the rate used for expense calculation + */ +function getDistanceMerchant( + hasRoute: boolean, + distanceInMeters: number, + unit: Unit, + rate: number, + currency: string, + translate: LocaleContextProps['translate'], + toLocaleDigit: LocaleContextProps['toLocaleDigit'], +): string { + const distanceInUnits = hasRoute ? getRoundedDistanceInUnits(distanceInMeters, unit) : translate('common.tbd'); + + const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); + const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; + const ratePerUnit = rate ? PolicyUtils.getUnitRateValue({rate}, toLocaleDigit) : translate('common.tbd'); + const currencySymbol = rate ? CurrencyUtils.getCurrencySymbol(currency) ?? `${currency} ` : ''; + + return `${distanceInUnits} ${unitString} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; +} + +/** + * Calculates the request amount based on distance, unit, and rate. + * + * @param distance - The distance traveled in meters + * @param unit - The unit of measurement for the distance + * @param rate - Rate used for calculating the request amount + * @returns The computed request amount (rounded) in "cents". + */ +function getDistanceRequestAmount(distance: number, unit: Unit, rate: number): number { + const convertedDistance = convertDistanceUnit(distance, unit); + const roundedDistance = parseFloat(convertedDistance.toFixed(2)); + return Math.round(roundedDistance * rate); +} + +export default {getDefaultMileageRate, getDistanceMerchant, getDistanceRequestAmount}; diff --git a/src/libs/HeaderUtils.ts b/src/libs/HeaderUtils.ts index 39dc0bc706e3..f7779b6db52f 100644 --- a/src/libs/HeaderUtils.ts +++ b/src/libs/HeaderUtils.ts @@ -12,17 +12,18 @@ type MenuItem = { }; function getPinMenuItem(report: OnyxReport): MenuItem { - if (!report.isPinned) { + const isPinned = report?.isPinned; + if (!isPinned) { return { icon: Expensicons.Pin, text: Localize.translateLocal('common.pin'), - onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, report.isPinned)), + onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, !!isPinned)), }; } return { icon: Expensicons.Pin, text: Localize.translateLocal('common.unPin'), - onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, report.isPinned)), + onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, isPinned)), }; } diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index e6e3240d1b23..e7550713710e 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -2,6 +2,23 @@ import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import * as OnyxCommon from './OnyxCommon'; +type Unit = 'mi' | 'km'; + +type Rate = { + name: string; + rate: number; + currency: string; +}; + +type CustomUnit = { + customUnitID?: string; + name?: string; + attributes: { + unit: Unit; + }; + rates?: Record; +}; + type Policy = { /** The ID of the policy */ id: string; @@ -43,7 +60,7 @@ type Policy = { lastModified?: string; /** The custom units data for this policy */ - customUnits?: Record; + customUnits?: Record; /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ areChatRoomsEnabled: boolean; @@ -62,3 +79,5 @@ type Policy = { }; export default Policy; + +export {Unit}; From 4de2b6a80b3ab93d5c2b7529328cc8568a797161 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 30 Nov 2023 15:51:04 +0100 Subject: [PATCH 05/15] Migrate src/libs/shouldReopenOnfido --- src/libs/shouldReopenOnfido/index.android.js | 1 - src/libs/shouldReopenOnfido/index.android.ts | 5 +++++ src/libs/shouldReopenOnfido/index.js | 1 - src/libs/shouldReopenOnfido/index.ts | 5 +++++ src/libs/shouldReopenOnfido/types.ts | 3 +++ src/types/onyx/Policy.ts | 2 +- 6 files changed, 14 insertions(+), 3 deletions(-) delete mode 100644 src/libs/shouldReopenOnfido/index.android.js create mode 100644 src/libs/shouldReopenOnfido/index.android.ts delete mode 100644 src/libs/shouldReopenOnfido/index.js create mode 100644 src/libs/shouldReopenOnfido/index.ts create mode 100644 src/libs/shouldReopenOnfido/types.ts diff --git a/src/libs/shouldReopenOnfido/index.android.js b/src/libs/shouldReopenOnfido/index.android.js deleted file mode 100644 index ff3177babdde..000000000000 --- a/src/libs/shouldReopenOnfido/index.android.js +++ /dev/null @@ -1 +0,0 @@ -export default true; diff --git a/src/libs/shouldReopenOnfido/index.android.ts b/src/libs/shouldReopenOnfido/index.android.ts new file mode 100644 index 000000000000..11f562575b08 --- /dev/null +++ b/src/libs/shouldReopenOnfido/index.android.ts @@ -0,0 +1,5 @@ +import ShouldReopenOnfido from './types'; + +const shouldReopenOnfido: ShouldReopenOnfido = true; + +export default shouldReopenOnfido; diff --git a/src/libs/shouldReopenOnfido/index.js b/src/libs/shouldReopenOnfido/index.js deleted file mode 100644 index 33136544dba2..000000000000 --- a/src/libs/shouldReopenOnfido/index.js +++ /dev/null @@ -1 +0,0 @@ -export default false; diff --git a/src/libs/shouldReopenOnfido/index.ts b/src/libs/shouldReopenOnfido/index.ts new file mode 100644 index 000000000000..24322b57df19 --- /dev/null +++ b/src/libs/shouldReopenOnfido/index.ts @@ -0,0 +1,5 @@ +import ShouldReopenOnfido from './types'; + +const shouldReopenOnfido: ShouldReopenOnfido = false; + +export default shouldReopenOnfido; diff --git a/src/libs/shouldReopenOnfido/types.ts b/src/libs/shouldReopenOnfido/types.ts new file mode 100644 index 000000000000..365fb95bb333 --- /dev/null +++ b/src/libs/shouldReopenOnfido/types.ts @@ -0,0 +1,3 @@ +type ShouldReopenOnfido = boolean; + +export default ShouldReopenOnfido; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index e7550713710e..a55a7c052b01 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -80,4 +80,4 @@ type Policy = { export default Policy; -export {Unit}; +export type {Unit}; From 478c580550f03227133e416737846873e87cae17 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 30 Nov 2023 16:51:26 +0100 Subject: [PATCH 06/15] Migrate RenameReceiptFilename migration --- src/libs/migrations/RenameReceiptFilename.js | 57 -------------------- src/libs/migrations/RenameReceiptFilename.ts | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 57 deletions(-) delete mode 100644 src/libs/migrations/RenameReceiptFilename.js create mode 100644 src/libs/migrations/RenameReceiptFilename.ts diff --git a/src/libs/migrations/RenameReceiptFilename.js b/src/libs/migrations/RenameReceiptFilename.js deleted file mode 100644 index 2fcd9662a993..000000000000 --- a/src/libs/migrations/RenameReceiptFilename.js +++ /dev/null @@ -1,57 +0,0 @@ -import lodashHas from 'lodash/has'; -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import Log from '@libs/Log'; -import ONYXKEYS from '@src/ONYXKEYS'; - -// This migration changes the property name on a transaction from receiptFilename to filename so that it matches what is stored in the database -export default function () { - return new Promise((resolve) => { - // Connect to the TRANSACTION collection key in Onyx to get all of the stored transactions. - // Go through each transaction and change the property name - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (transactions) => { - Onyx.disconnect(connectionID); - - if (!transactions || transactions.length === 0) { - Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there are no transactions'); - return resolve(); - } - - if (!_.compact(_.pluck(transactions, 'receiptFilename')).length) { - Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there were no transactions with the receiptFilename property'); - return resolve(); - } - - Log.info('[Migrate Onyx] Running RenameReceiptFilename migration'); - - const dataToSave = _.reduce( - transactions, - (result, transaction) => { - // Do nothing if there is no receiptFilename property - if (!lodashHas(transaction, 'receiptFilename')) { - return result; - } - Log.info(`[Migrate Onyx] Renaming receiptFilename ${transaction.receiptFilename} to filename`); - return { - ...result, - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: { - filename: transaction.receiptFilename, - receiptFilename: null, - }, - }; - }, - {}, - ); - - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, dataToSave).then(() => { - Log.info(`[Migrate Onyx] Ran migration RenameReceiptFilename and renamed ${_.size(dataToSave)} properties`); - resolve(); - }); - }, - }); - }); -} diff --git a/src/libs/migrations/RenameReceiptFilename.ts b/src/libs/migrations/RenameReceiptFilename.ts new file mode 100644 index 000000000000..5c18647ffe57 --- /dev/null +++ b/src/libs/migrations/RenameReceiptFilename.ts @@ -0,0 +1,56 @@ +import Onyx from 'react-native-onyx'; +import {NullishDeep, OnyxCollection} from 'react-native-onyx/lib/types'; +import Log from '@libs/Log'; +import ONYXKEYS from '@src/ONYXKEYS'; +import Transaction from '@src/types/onyx/Transaction'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type OldTransaction = Transaction & {receiptFilename?: string}; + +// This migration changes the property name on a transaction from receiptFilename to filename so that it matches what is stored in the database +export default function () { + return new Promise((resolve) => { + // Connect to the TRANSACTION collection key in Onyx to get all of the stored transactions. + // Go through each transaction and change the property name + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (transactions: OnyxCollection) => { + Onyx.disconnect(connectionID); + + if (!transactions || isEmptyObject(transactions)) { + Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there are no transactions'); + return resolve(); + } + + const transactionArray: Array = Object.values(transactions); + if (!transactionArray?.map((transaction) => transaction?.receiptFilename).filter(Boolean).length) { + Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there were no transactions with the receiptFilename property'); + return resolve(); + } + + Log.info('[Migrate Onyx] Running RenameReceiptFilename migration'); + const dataToSave: Record<`${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`, NullishDeep> = {}; + + transactionArray?.forEach((transaction) => { + // Do nothing if there is no receiptFilename property + if (!transaction || !('receiptFilename' in transaction)) { + return; + } + + Log.info(`[Migrate Onyx] Renaming receiptFilename ${transaction?.receiptFilename} to filename`); + dataToSave[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`] = { + filename: transaction?.receiptFilename, + receiptFilename: null, + }; + }, {}); + + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, dataToSave).then(() => { + Log.info(`[Migrate Onyx] Ran migration RenameReceiptFilename and renamed ${Object.keys(dataToSave)?.length} properties`); + resolve(); + }); + }, + }); + }); +} From 73e9c92eb423adc27e91155a1e088f2aa940932f Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 30 Nov 2023 17:05:31 +0100 Subject: [PATCH 07/15] Migrate KeyReportActionsDraftByReportActionID migration --- ... KeyReportActionsDraftByReportActionID.ts} | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) rename src/libs/migrations/{KeyReportActionsDraftByReportActionID.js => KeyReportActionsDraftByReportActionID.ts} (61%) diff --git a/src/libs/migrations/KeyReportActionsDraftByReportActionID.js b/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts similarity index 61% rename from src/libs/migrations/KeyReportActionsDraftByReportActionID.js rename to src/libs/migrations/KeyReportActionsDraftByReportActionID.ts index e4b3ebd060f3..944b9d3e5478 100644 --- a/src/libs/migrations/KeyReportActionsDraftByReportActionID.js +++ b/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts @@ -1,21 +1,21 @@ -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; +import {ReportActionsDrafts} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; /** * This migration updates reportActionsDrafts data to be keyed by reportActionID. * * Before: reportActionsDrafts_reportID_reportActionID: value * After: reportActionsDrafts_reportID: {[reportActionID]: value} - * - * @returns {Promise} */ export default function () { - return new Promise((resolve) => { + return new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS, waitForCollectionCallback: true, + // eslint-disable-next-line @typescript-eslint/no-misused-promises callback: (allReportActionsDrafts) => { Onyx.disconnect(connectionID); @@ -24,34 +24,39 @@ export default function () { return resolve(); } - const newReportActionsDrafts = {}; - _.each(allReportActionsDrafts, (reportActionDraft, onyxKey) => { - if (!_.isString(reportActionDraft)) { + type ReportActionsDraftsKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${string}`; + const newReportActionsDrafts: Record> = {}; + Object.entries(allReportActionsDrafts).forEach(([onyxKey, reportActionDraft]) => { + if (typeof reportActionDraft !== 'string') { return; } - newReportActionsDrafts[onyxKey] = null; + newReportActionsDrafts[onyxKey as ReportActionsDraftsKey] = null; - if (_.isEmpty(reportActionDraft)) { + if (isEmptyObject(reportActionDraft)) { return; } const reportActionID = onyxKey.split('_').pop(); - const newOnyxKey = onyxKey.replace(`_${reportActionID}`, ''); + const newOnyxKey = onyxKey.replace(`_${reportActionID}`, '') as ReportActionsDraftsKey; + + if (!reportActionID) { + return; + } // If newReportActionsDrafts[newOnyxKey] isn't set, fall back on the migrated draft if there is one - const currentActionsDrafts = newReportActionsDrafts[newOnyxKey] || allReportActionsDrafts[newOnyxKey]; + const currentActionsDrafts = newReportActionsDrafts[newOnyxKey] ?? allReportActionsDrafts[newOnyxKey]; newReportActionsDrafts[newOnyxKey] = { ...currentActionsDrafts, [reportActionID]: reportActionDraft, }; }); - if (_.isEmpty(newReportActionsDrafts)) { + if (isEmptyObject(newReportActionsDrafts)) { Log.info('[Migrate Onyx] Skipped migration KeyReportActionsDraftByReportActionID because there are no actions drafts to migrate'); return resolve(); } - Log.info(`[Migrate Onyx] Re-keying reportActionsDrafts by reportActionID for ${_.keys(newReportActionsDrafts).length} actions drafts`); + Log.info(`[Migrate Onyx] Re-keying reportActionsDrafts by reportActionID for ${Object.keys(newReportActionsDrafts).length} actions drafts`); // eslint-disable-next-line rulesdir/prefer-actions-set-data return Onyx.multiSet(newReportActionsDrafts).then(resolve); }, From 276eef40cf8706d7c6c7b2744dd0198839c1d76c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 30 Nov 2023 17:23:04 +0100 Subject: [PATCH 08/15] Migrate UpdateMultilineInputRange --- .../{index.ios.js => index.ios.ts} | 11 ++++++----- .../{index.js => index.ts} | 14 +++++++------- src/libs/UpdateMultilineInputRange/types.ts | 5 +++++ 3 files changed, 18 insertions(+), 12 deletions(-) rename src/libs/UpdateMultilineInputRange/{index.ios.js => index.ios.ts} (82%) rename src/libs/UpdateMultilineInputRange/{index.js => index.ts} (75%) create mode 100644 src/libs/UpdateMultilineInputRange/types.ts diff --git a/src/libs/UpdateMultilineInputRange/index.ios.js b/src/libs/UpdateMultilineInputRange/index.ios.ts similarity index 82% rename from src/libs/UpdateMultilineInputRange/index.ios.js rename to src/libs/UpdateMultilineInputRange/index.ios.ts index 4c10f768a2a2..cbe271162372 100644 --- a/src/libs/UpdateMultilineInputRange/index.ios.js +++ b/src/libs/UpdateMultilineInputRange/index.ios.ts @@ -1,3 +1,5 @@ +import UpdateMultilineInputRange from './types'; + /** * Place the cursor at the end of the value (if there is a value in the input). * @@ -6,11 +8,8 @@ * focus. This provides a better user experience in cases where the text in the field has to be edited. The auto- * scroll behaviour works on all platforms except iOS native. * See https://github.com/Expensify/App/issues/20836 for more details. - * - * @param {Object} input the input element - * @param {boolean} shouldAutoFocus */ -export default function updateMultilineInputRange(input, shouldAutoFocus = true) { +const updateMultilineInputRange: UpdateMultilineInputRange = (input, shouldAutoFocus = true) => { if (!input) { return; } @@ -23,4 +22,6 @@ export default function updateMultilineInputRange(input, shouldAutoFocus = true) if (shouldAutoFocus) { input.focus(); } -} +}; + +export default updateMultilineInputRange; diff --git a/src/libs/UpdateMultilineInputRange/index.js b/src/libs/UpdateMultilineInputRange/index.ts similarity index 75% rename from src/libs/UpdateMultilineInputRange/index.js rename to src/libs/UpdateMultilineInputRange/index.ts index 66fb1889be21..30b98c05744f 100644 --- a/src/libs/UpdateMultilineInputRange/index.js +++ b/src/libs/UpdateMultilineInputRange/index.ts @@ -1,3 +1,5 @@ +import UpdateMultilineInputRange from './types'; + /** * Place the cursor at the end of the value (if there is a value in the input). * @@ -6,20 +8,18 @@ * focus. This provides a better user experience in cases where the text in the field has to be edited. The auto- * scroll behaviour works on all platforms except iOS native. * See https://github.com/Expensify/App/issues/20836 for more details. - * - * @param {Object} input the input element - * @param {boolean} shouldAutoFocus */ -// eslint-disable-next-line no-unused-vars -export default function updateMultilineInputRange(input, shouldAutoFocus = true) { +const updateMultilineInputRange: UpdateMultilineInputRange = (input) => { if (!input) { return; } - if (input.value && input.setSelectionRange) { + if ('value' in input && input.value && input.setSelectionRange) { const length = input.value.length; input.setSelectionRange(length, length); // eslint-disable-next-line no-param-reassign input.scrollTop = input.scrollHeight; } -} +}; + +export default updateMultilineInputRange; diff --git a/src/libs/UpdateMultilineInputRange/types.ts b/src/libs/UpdateMultilineInputRange/types.ts new file mode 100644 index 000000000000..95b5a8bce744 --- /dev/null +++ b/src/libs/UpdateMultilineInputRange/types.ts @@ -0,0 +1,5 @@ +import {TextInput} from 'react-native'; + +type UpdateMultilineInputRange = (input: HTMLInputElement | TextInput, shouldAutoFocus?: boolean) => void; + +export default UpdateMultilineInputRange; From a313241741f55ae6a38ab31d68d46b1a3ebfbd72 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 1 Dec 2023 14:24:28 +0100 Subject: [PATCH 09/15] Adjust the code after review --- src/libs/DistanceRequestUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index ad0b814f1e5c..682567c4c6ba 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -25,12 +25,12 @@ function getDefaultMileageRate(policy: Policy): DefaultMileageRate | null { return null; } - const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - if (!distanceUnit) { + const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceUnit?.rates) { return null; } - const distanceRate = Object.values(distanceUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const distanceRate = Object.values(distanceUnit.rates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); if (!distanceRate) { return null; } From 17f59baf4cb0b3b46ca18b6e5685418e9763fe90 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Dec 2023 14:07:31 +0100 Subject: [PATCH 10/15] Add OnyxEntry to functions, adjust ?? to || --- src/libs/DistanceRequestUtils.ts | 6 ++++-- src/libs/HeaderUtils.ts | 3 ++- src/libs/NextStepUtils.ts | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 682567c4c6ba..477bfb4b61f9 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,3 +1,4 @@ +import {OnyxEntry} from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; import Policy, {Unit} from '@src/types/onyx/Policy'; @@ -20,7 +21,7 @@ type DefaultMileageRate = { * @returns [currency] - The currency associated with the rate. * @returns [unit] - The unit of measurement for the distance. */ -function getDefaultMileageRate(policy: Policy): DefaultMileageRate | null { +function getDefaultMileageRate(policy: OnyxEntry): DefaultMileageRate | null { if (!policy?.customUnits) { return null; } @@ -99,7 +100,8 @@ function getDistanceMerchant( const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; const ratePerUnit = rate ? PolicyUtils.getUnitRateValue({rate}, toLocaleDigit) : translate('common.tbd'); - const currencySymbol = rate ? CurrencyUtils.getCurrencySymbol(currency) ?? `${currency} ` : ''; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const currencySymbol = rate ? CurrencyUtils.getCurrencySymbol(currency) || `${currency} ` : ''; return `${distanceInUnits} ${unitString} @ ${currencySymbol}${ratePerUnit} / ${singularDistanceUnit}`; } diff --git a/src/libs/HeaderUtils.ts b/src/libs/HeaderUtils.ts index f7779b6db52f..c8dfc20df204 100644 --- a/src/libs/HeaderUtils.ts +++ b/src/libs/HeaderUtils.ts @@ -1,3 +1,4 @@ +import {OnyxEntry} from 'react-native-onyx'; import {SvgProps} from 'react-native-svg'; import * as Expensicons from '@components/Icon/Expensicons'; import OnyxReport from '@src/types/onyx/Report'; @@ -11,7 +12,7 @@ type MenuItem = { onSelected: () => void; }; -function getPinMenuItem(report: OnyxReport): MenuItem { +function getPinMenuItem(report: OnyxEntry): MenuItem { const isPinned = report?.isPinned; if (!isPinned) { return { diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 1d99da633a4e..538e8dbe81f0 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -5,11 +5,12 @@ type Message = { type?: string; }; -function parseMessage(messages: Message[]) { +function parseMessage(messages: Message[] | undefined) { let nextStepHTML = ''; messages?.forEach((part) => { - const tagType = part.type ?? 'span'; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const tagType = part.type || 'span'; nextStepHTML += `<${tagType}>${Str.safeEscape(part.text)}`; }); From 688dec676aeda7f4e9a3b18ff8aca2a251e4d34d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Dec 2023 14:10:19 +0100 Subject: [PATCH 11/15] Rename '@libs/UpdateMultilineInputRange' --- .../index.ios.ts | 0 .../index.ts | 0 .../types.ts | 0 src/pages/EditRequestDescriptionPage.js | 2 +- src/pages/PrivateNotes/PrivateNotesEditPage.js | 2 +- src/pages/ReportWelcomeMessagePage.js | 2 +- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 2 +- src/pages/iou/MoneyRequestDescriptionPage.js | 2 +- src/pages/tasks/NewTaskDescriptionPage.js | 2 +- src/pages/tasks/TaskDescriptionPage.js | 2 +- 10 files changed, 7 insertions(+), 7 deletions(-) rename src/libs/{UpdateMultilineInputRange => updateMultilineInputRange}/index.ios.ts (100%) rename src/libs/{UpdateMultilineInputRange => updateMultilineInputRange}/index.ts (100%) rename src/libs/{UpdateMultilineInputRange => updateMultilineInputRange}/types.ts (100%) diff --git a/src/libs/UpdateMultilineInputRange/index.ios.ts b/src/libs/updateMultilineInputRange/index.ios.ts similarity index 100% rename from src/libs/UpdateMultilineInputRange/index.ios.ts rename to src/libs/updateMultilineInputRange/index.ios.ts diff --git a/src/libs/UpdateMultilineInputRange/index.ts b/src/libs/updateMultilineInputRange/index.ts similarity index 100% rename from src/libs/UpdateMultilineInputRange/index.ts rename to src/libs/updateMultilineInputRange/index.ts diff --git a/src/libs/UpdateMultilineInputRange/types.ts b/src/libs/updateMultilineInputRange/types.ts similarity index 100% rename from src/libs/UpdateMultilineInputRange/types.ts rename to src/libs/updateMultilineInputRange/types.ts diff --git a/src/pages/EditRequestDescriptionPage.js b/src/pages/EditRequestDescriptionPage.js index f6779ac83ead..1f60be85ce94 100644 --- a/src/pages/EditRequestDescriptionPage.js +++ b/src/pages/EditRequestDescriptionPage.js @@ -9,7 +9,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import * as Browser from '@libs/Browser'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.js b/src/pages/PrivateNotes/PrivateNotesEditPage.js index 2c3064d199ef..dce572c7dd6c 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.js +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.js @@ -19,7 +19,7 @@ import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import reportPropTypes from '@pages/reportPropTypes'; diff --git a/src/pages/ReportWelcomeMessagePage.js b/src/pages/ReportWelcomeMessagePage.js index a0e471d00df6..17329139c63f 100644 --- a/src/pages/ReportWelcomeMessagePage.js +++ b/src/pages/ReportWelcomeMessagePage.js @@ -14,7 +14,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import useThemeStyles from '@styles/useThemeStyles'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index ea48f9cc931e..ba50f5503aff 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -22,7 +22,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionUtils from '@libs/SuggestionUtils'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside'; import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater'; import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions'; diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index 43d7b58bfc7f..3937de006d8d 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -15,7 +15,7 @@ import * as IOU from '@libs/actions/IOU'; import * as Browser from '@libs/Browser'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index 002ca2944e52..09ecbc6284e6 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -13,7 +13,7 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import useThemeStyles from '@styles/useThemeStyles'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 6901f2562ee3..93d8828ff662 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.js @@ -16,7 +16,7 @@ import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; -import updateMultilineInputRange from '@libs/UpdateMultilineInputRange'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import withReportOrNotFound from '@pages/home/report/withReportOrNotFound'; import reportPropTypes from '@pages/reportPropTypes'; import useThemeStyles from '@styles/useThemeStyles'; From 30d6aadc7abb3413d10a0c6bed7ab4a02b435a44 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Dec 2023 14:21:29 +0100 Subject: [PATCH 12/15] Fix getPinMenuItem logic and typecheck failing --- src/libs/HeaderUtils.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/libs/HeaderUtils.ts b/src/libs/HeaderUtils.ts index c8dfc20df204..d258cecb3d11 100644 --- a/src/libs/HeaderUtils.ts +++ b/src/libs/HeaderUtils.ts @@ -12,18 +12,16 @@ type MenuItem = { onSelected: () => void; }; -function getPinMenuItem(report: OnyxEntry): MenuItem { - const isPinned = report?.isPinned; - if (!isPinned) { - return { - icon: Expensicons.Pin, - text: Localize.translateLocal('common.pin'), - onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, !!isPinned)), - }; +function getPinMenuItem(report: OnyxEntry): MenuItem | undefined { + if (!report) { + return; } + + const isPinned = !!report?.isPinned; + return { icon: Expensicons.Pin, - text: Localize.translateLocal('common.unPin'), + text: Localize.translateLocal(isPinned ? 'common.pin' : 'common.unPin'), onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, isPinned)), }; } From baff909425d1e52a62b10dc6604b451ac2d0a4e2 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 5 Dec 2023 14:26:43 +0100 Subject: [PATCH 13/15] Remove optional chaining --- src/libs/HeaderUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/HeaderUtils.ts b/src/libs/HeaderUtils.ts index d258cecb3d11..57c7bc22a31e 100644 --- a/src/libs/HeaderUtils.ts +++ b/src/libs/HeaderUtils.ts @@ -17,7 +17,7 @@ function getPinMenuItem(report: OnyxEntry): MenuItem | undefined { return; } - const isPinned = !!report?.isPinned; + const isPinned = !!report.isPinned; return { icon: Expensicons.Pin, From c106a013fcc2db3aba1c3f11a89405c44dd0733b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 7 Dec 2023 10:00:55 +0100 Subject: [PATCH 14/15] Fix pin menu --- src/libs/HeaderUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/HeaderUtils.ts b/src/libs/HeaderUtils.ts index 57c7bc22a31e..6ef8445f707b 100644 --- a/src/libs/HeaderUtils.ts +++ b/src/libs/HeaderUtils.ts @@ -21,7 +21,7 @@ function getPinMenuItem(report: OnyxEntry): MenuItem | undefined { return { icon: Expensicons.Pin, - text: Localize.translateLocal(isPinned ? 'common.pin' : 'common.unPin'), + text: Localize.translateLocal(isPinned ? 'common.unPin' : 'common.pin'), onSelected: Session.checkIfActionIsAllowed(() => Report.togglePinnedState(report.reportID, isPinned)), }; } From 8f06dadd5cae24fa70de74f1fda8fcc20b96ce23 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 8 Dec 2023 10:12:16 +0100 Subject: [PATCH 15/15] Clean migration files code --- .../KeyReportActionsDraftByReportActionID.ts | 3 ++- src/libs/migrations/RenameReceiptFilename.ts | 27 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts b/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts index 944b9d3e5478..1abe5a6114bb 100644 --- a/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts +++ b/src/libs/migrations/KeyReportActionsDraftByReportActionID.ts @@ -4,6 +4,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import {ReportActionsDrafts} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +type ReportActionsDraftsKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${string}`; + /** * This migration updates reportActionsDrafts data to be keyed by reportActionID. * @@ -24,7 +26,6 @@ export default function () { return resolve(); } - type ReportActionsDraftsKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${string}`; const newReportActionsDrafts: Record> = {}; Object.entries(allReportActionsDrafts).forEach(([onyxKey, reportActionDraft]) => { if (typeof reportActionDraft !== 'string') { diff --git a/src/libs/migrations/RenameReceiptFilename.ts b/src/libs/migrations/RenameReceiptFilename.ts index 5c18647ffe57..b2c19848aedb 100644 --- a/src/libs/migrations/RenameReceiptFilename.ts +++ b/src/libs/migrations/RenameReceiptFilename.ts @@ -6,6 +6,7 @@ import Transaction from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type OldTransaction = Transaction & {receiptFilename?: string}; +type TransactionKey = `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`; // This migration changes the property name on a transaction from receiptFilename to filename so that it matches what is stored in the database export default function () { @@ -23,25 +24,23 @@ export default function () { return resolve(); } - const transactionArray: Array = Object.values(transactions); - if (!transactionArray?.map((transaction) => transaction?.receiptFilename).filter(Boolean).length) { + const transactionsWithReceipt: Array = Object.values(transactions).filter((transaction) => transaction?.receiptFilename); + if (!transactionsWithReceipt?.length) { Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there were no transactions with the receiptFilename property'); return resolve(); } - Log.info('[Migrate Onyx] Running RenameReceiptFilename migration'); - const dataToSave: Record<`${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`, NullishDeep> = {}; - - transactionArray?.forEach((transaction) => { - // Do nothing if there is no receiptFilename property - if (!transaction || !('receiptFilename' in transaction)) { - return; + const dataToSave: Record> = transactionsWithReceipt?.reduce((result, transaction) => { + if (!transaction) { + return result; } - - Log.info(`[Migrate Onyx] Renaming receiptFilename ${transaction?.receiptFilename} to filename`); - dataToSave[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`] = { - filename: transaction?.receiptFilename, - receiptFilename: null, + Log.info(`[Migrate Onyx] Renaming receiptFilename ${transaction.receiptFilename} to filename`); + return { + ...result, + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: { + filename: transaction.receiptFilename, + receiptFilename: null, + }, }; }, {});