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

Report actions Onyx migration for accountIDs #21509

Merged
merged 13 commits into from
Jun 26, 2023
2 changes: 2 additions & 0 deletions src/libs/migrateOnyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import MoveToIndexedDB from './migrations/MoveToIndexedDB';
import RenameExpensifyNewsStatus from './migrations/RenameExpensifyNewsStatus';
import AddLastVisibleActionCreated from './migrations/AddLastVisibleActionCreated';
import KeyReportActionsByReportActionID from './migrations/KeyReportActionsByReportActionID';
import PersonalDetailsByAccountID from './migrations/PersonalDetailsByAccountID';

export default function () {
const startTime = Date.now();
Expand All @@ -22,6 +23,7 @@ export default function () {
RenameExpensifyNewsStatus,
AddLastVisibleActionCreated,
KeyReportActionsByReportActionID,
PersonalDetailsByAccountID,
];

// Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the
Expand Down
213 changes: 213 additions & 0 deletions src/libs/migrations/PersonalDetailsByAccountID.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import _ from 'underscore';
import lodashHas from 'lodash/has';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
import Log from '../Log';

const DEPRECATED_ONYX_KEYS = {
// Deprecated personal details object which was keyed by login instead of accountID.
PERSONAL_DETAILS: 'personalDetails',
};

/**
* @returns {Promise<Object>}
*/
function getReportActionsFromOnyx() {
return new Promise((resolve) => {
const connectionID = Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (allReportActions) => {
Onyx.disconnect(connectionID);
return resolve(allReportActions);
},
});
});
}

/**
* We use the old personalDetails object becuase it is more efficient for this migration since it is keyed by email address.
* Also, if the old personalDetails object is not available, that likely means the migration has already run successfully before on this account.
*
* @returns {Promise<Object>}
*/
function getDeprecatedPersonalDetailsFromOnyx() {
return new Promise((resolve) => {
const connectionID = Onyx.connect({
key: DEPRECATED_ONYX_KEYS.PERSONAL_DETAILS,
puneetlath marked this conversation as resolved.
Show resolved Hide resolved
callback: (allPersonalDetails) => {
Onyx.disconnect(connectionID);
return resolve(allPersonalDetails);
},
});
});
}

/**
* Migrate Onyx data for the email to accountID migration.
*
* - reportAction_
* - originalMessage.oldLogin -> originalMessage.oldAccountID
* - originalMessage.newLogin -> originalMessage.newAccountID
* - accountEmail -> accountID
* - actorEmail -> actorAccountID
* - childManagerEmail -> childManagerAccountID
* - whisperedTo -> whisperedToAccountIDs
* - childOldestFourEmails -> childOldestFourAccountIDs
* - originalMessage.participants -> originalMessage.participantAccountIDs
puneetlath marked this conversation as resolved.
Show resolved Hide resolved
*
* @returns {Promise<void>}
*/
export default function () {
return Promise.all([getReportActionsFromOnyx(), getDeprecatedPersonalDetailsFromOnyx()]).then(([oldReportActions, oldPersonalDetails]) => {
const onyxData = {};

if (!oldReportActions) {
Log.info('[Migrate Onyx] Skipped migration PersonalDetailsByAccountID because there were no reportActions');
return;
}

// We migrate reportActions to have the new accountID-based data if they don't already.
// If we are not able to get the accountID from personalDetails for some reason, we will just clear the reportAction
// and let it be fetched from the API next time they open the report and scroll to that action.
puneetlath marked this conversation as resolved.
Show resolved Hide resolved
// We do this because we know the reportAction from the API will include the needed accountID data.
_.each(oldReportActions, (reportActionsForReport, onyxKey) => {
if (_.isEmpty(reportActionsForReport)) {
Log.info(`[Migrate Onyx] Skipped migration PersonalDetailsByAccountID for ${onyxKey} because there were no reportActions`);
return;
}

const newReportActionsForReport = {};
let reportActionsWereModified = false;
_.each(reportActionsForReport, (reportAction, reportActionID) => {
if (_.isEmpty(reportAction)) {
reportActionsWereModified = true;
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because the reportAction was empty`);
return;
}

const newReportAction = reportAction;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why we are adding a new variable here? Both newReportAction and reportAction refer to the same object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ESLint doesn't like it if I assign to a function parameter.

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm but under the hood we are doing exactly that right? if you console.log reportAction it will be exactly the same as newReportAction

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. Do you see it as a problem though? Is there a reason for me to copy the object instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, the objects here are used only once. All good 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!


if (lodashHas(reportAction, ['originalMessage', 'oldLogin']) && !lodashHas(reportAction, ['originalMessage', 'oldAccountID'])) {
reportActionsWereModified = true;
const oldAccountID = _.get(oldPersonalDetails, [reportAction.originalMessage.oldLogin, 'accountID']);
if (oldAccountID) {
newReportAction.originalMessage.oldAccountID = oldAccountID;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because originalMessage.oldAccountID not found`);
return;
}
}

if (lodashHas(reportAction, ['originalMessage', 'newLogin']) && !lodashHas(reportAction, ['originalMessage', 'newAccountID'])) {
reportActionsWereModified = true;
const newAccountID = _.get(oldPersonalDetails, [reportAction.originalMessage.newLogin, 'accountID']);
if (newAccountID) {
newReportAction.originalMessage.newAccountID = newAccountID;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because originalMessage.newAccountID not found`);
return;
}
}

if (lodashHas(reportAction, ['accountEmail']) && !lodashHas(reportAction, ['accountID'])) {
reportActionsWereModified = true;
const accountID = _.get(oldPersonalDetails, [reportAction.accountEmail, 'accountID']);
if (accountID) {
newReportAction.accountID = accountID;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because accountID not found`);
return;
}
}

if (lodashHas(reportAction, ['actorEmail']) && !lodashHas(reportAction, ['actorAccountID'])) {
reportActionsWereModified = true;
const actorAccountID = _.get(oldPersonalDetails, [reportAction.actorEmail, 'accountID']);
if (actorAccountID) {
newReportAction.actorAccountID = actorAccountID;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because actorAccountID not found`);
return;
}
}

if (lodashHas(reportAction, ['childManagerEmail']) && !lodashHas(reportAction, ['childManagerAccountID'])) {
reportActionsWereModified = true;
const childManagerAccountID = _.get(oldPersonalDetails, [reportAction.childManagerEmail, 'accountID']);
if (childManagerAccountID) {
newReportAction.childManagerAccountID = childManagerAccountID;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because childManagerAccountID not found`);
return;
}
}

if (lodashHas(reportAction, ['whisperedTo']) && !lodashHas(reportAction, ['whisperedToAccountIDs'])) {
reportActionsWereModified = true;
const whisperedToAccountIDs = [];
_.each(reportAction.whisperedTo, (whisperedToLogin) => {
const whisperedToAccountID = _.get(oldPersonalDetails, [whisperedToLogin, 'accountID']);
if (whisperedToAccountID) {
whisperedToAccountIDs.push(whisperedToAccountID);
}
});

if (whisperedToAccountIDs.length === reportAction.whisperedTo.length) {
newReportAction.whisperedToAccountIDs = whisperedToAccountIDs;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because whisperedToAccountIDs not found`);
return;
}
}

if (lodashHas(reportAction, ['childOldestFourEmails']) && !lodashHas(reportAction, ['childOldestFourAccountIDs'])) {
reportActionsWereModified = true;
const childOldestFourEmails = reportAction.childOldestFourEmails.split(',');
puneetlath marked this conversation as resolved.
Show resolved Hide resolved
const childOldestFourAccountIDs = [];
_.each(childOldestFourEmails, (login) => {
const accountID = _.get(oldPersonalDetails, [login.trim(), 'accountID']);
if (accountID) {
childOldestFourAccountIDs.push(accountID);
}
});

if (childOldestFourAccountIDs.length === childOldestFourEmails.length) {
newReportAction.childOldestFourAccountIDs = childOldestFourAccountIDs.join(',');
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because childOldestFourAccountIDs not found`);
return;
}
}

if (lodashHas(reportAction, ['originalMessage', 'participants']) && !lodashHas(reportAction, ['originalMessage', 'participantAccountIDs'])) {
reportActionsWereModified = true;
const participantAccountIDs = [];
_.each(reportAction.originalMessage.participants, (participant) => {
const participantAccountID = _.get(oldPersonalDetails, [participant, 'accountID']);
if (participantAccountID) {
participantAccountIDs.push(participantAccountID);
}
});

if (participantAccountIDs.length === reportAction.originalMessage.participants.length) {
newReportAction.originalMessage.participantAccountIDs = participantAccountIDs;
} else {
Log.info(`[Migrate Onyx] PersonalDetailsByAccountID migration: removing reportAction ${reportActionID} because originalMessage.participantAccountIDs not found`);
return;
}
}

newReportActionsForReport[reportActionID] = newReportAction;
});

// Only include the reportActions from this report if at least one reportAction in this report
// was modified in any way.
if (reportActionsWereModified) {
onyxData[onyxKey] = newReportActionsForReport;
}
});

return Onyx.multiSet(onyxData);
});
}
Loading