diff --git a/src/libs/migrations/CheckForPreviousReportActionID.js b/src/libs/migrations/CheckForPreviousReportActionID.js new file mode 100644 index 000000000000..a61d9bfb08ec --- /dev/null +++ b/src/libs/migrations/CheckForPreviousReportActionID.js @@ -0,0 +1,67 @@ +import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import Log from '../Log'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * @returns {Promise} + */ +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); + }, + }); + }); +} + +/** + * This migration checks for the 'previousReportActionID' key in the first valid reportAction of a report in Onyx. + * If the key is not found then all reportActions for all reports are removed from Onyx. + * + * @returns {Promise} + */ +export default function () { + return getReportActionsFromOnyx().then((allReportActions) => { + if (_.isEmpty(allReportActions)) { + Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions`); + return; + } + + let firstValidValue; + _.some(_.values(allReportActions), (reportActions) => + _.some(_.values(reportActions), (reportActionData) => { + if (_.has(reportActionData, 'reportActionID')) { + firstValidValue = reportActionData; + return true; + } + + return false; + }), + ); + + if (_.isUndefined(firstValidValue)) { + Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions`); + return; + } + + if (_.has(firstValidValue, 'previousReportActionID')) { + Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete`); + return; + } + + // If previousReportActionID not found: + Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first valid reportAction`); + + const onyxData = {}; + _.each(allReportActions, (reportAction, onyxKey) => { + onyxData[onyxKey] = {}; + }); + + return Onyx.multiSet(onyxData); + }); +} diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index af7f39ddf828..0171ee640226 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -8,6 +8,7 @@ import AddLastVisibleActionCreated from '../../src/libs/migrations/AddLastVisibl import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; import KeyReportActionsByReportActionID from '../../src/libs/migrations/KeyReportActionsByReportActionID'; import PersonalDetailsByAccountID from '../../src/libs/migrations/PersonalDetailsByAccountID'; +import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; import ONYXKEYS from '../../src/ONYXKEYS'; jest.mock('../../src/libs/getPlatform'); @@ -639,4 +640,174 @@ describe('Migrations', () => { }); })); }); + + describe('CheckForPreviousReportActionID', () => { + it("Should work even if there's no reportAction data in Onyx", () => + CheckForPreviousReportActionID().then(() => + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions'), + )); + + it('Should remove all report actions given that a previousReportActionID does not exist', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + 1: { + reportActionID: 1, + }, + 2: { + reportActionID: 2, + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith( + '[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first valid reportAction', + ); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = {}; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + }, + }); + })); + + it('Should not remove any report action given that previousReportActionID exists in first valid report action', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + 1: { + reportActionID: 1, + previousReportActionID: 0, + }, + 2: { + reportActionID: 2, + previousReportActionID: 1, + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = { + 1: { + reportActionID: 1, + previousReportActionID: 0, + }, + 2: { + reportActionID: 2, + previousReportActionID: 1, + }, + }; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + }, + }); + })); + + it('Should skip zombie report actions and proceed to remove all reportActions given that a previousReportActionID does not exist', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { + 1: { + reportActionID: 1, + }, + 2: { + reportActionID: 2, + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith( + '[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first valid reportAction', + ); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = {}; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction); + }, + }); + })); + + it('Should skip zombie report actions and should not remove any report action given that previousReportActionID exists in first valid report action', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { + 1: { + reportActionID: 1, + previousReportActionID: 10, + }, + 2: { + reportActionID: 2, + previousReportActionID: 23, + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction1 = {}; + const expectedReportAction4 = { + 1: { + reportActionID: 1, + previousReportActionID: 10, + }, + 2: { + reportActionID: 2, + previousReportActionID: 23, + }, + }; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction1); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction4); + }, + }); + })); + + it('Should skip if no valid reportActions', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: null, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = {}; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeNull(); + }, + }); + })); + }); });