Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Comment thread
puneetlath marked this conversation as 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
Comment thread
puneetlath marked this conversation as 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.
Comment thread
puneetlath marked this conversation as 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
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
Member

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
Copy Markdown
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(',');
Comment thread
puneetlath marked this conversation as 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