Skip to content
10 changes: 7 additions & 3 deletions src/libs/DateUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,14 @@ function getMicroseconds() {

/**
* Returns the current time in milliseconds in the format expected by the database
*
* @param {String|Number} [timestamp]
*
* @returns {String}
*/
function currentDBTime() {
return new Date().toISOString()
function getDBTime(timestamp = '') {
const datetime = timestamp ? new Date(timestamp) : new Date();
return datetime.toISOString()
.replace('T', ' ')
.replace('Z', '');
}
Expand All @@ -183,7 +187,7 @@ const DateUtils = {
canUpdateTimezone,
setTimezoneUpdated,
getMicroseconds,
currentDBTime,
getDBTime,
};

export default DateUtils;
2 changes: 1 addition & 1 deletion src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ function getOptions(reports, personalDetails, {
return -Infinity;
}

return report.lastMessageTimestamp;
return report.lastActionCreated;
});
orderedReports.reverse();

Expand Down
8 changes: 4 additions & 4 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ function buildOptimisticReportAction(sequenceNumber, text, file) {
sequenceNumber,
clientID: NumberUtils.generateReportActionClientID(),
avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)),
created: DateUtils.currentDBTime(),
created: DateUtils.getDBTime(),
message: [
{
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
Expand Down Expand Up @@ -795,7 +795,7 @@ function buildOptimisticIOUReportAction(sequenceNumber, type, amount, currency,
reportActionID: NumberUtils.rand64(),
sequenceNumber,
shouldShow: true,
created: DateUtils.currentDBTime(),
created: DateUtils.getDBTime(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
}
Expand Down Expand Up @@ -834,7 +834,7 @@ function buildOptimisticChatReport(
lastMessageHtml: '',
lastMessageText: null,
lastReadSequenceNumber: 0,
lastMessageTimestamp: 0,
lastActionCreated: '',
lastVisitedTimestamp: 0,
maxSequenceNumber: 0,
notificationPreference,
Expand Down Expand Up @@ -883,7 +883,7 @@ function buildOptimisticCreatedReportAction(ownerEmail) {
automatic: false,
sequenceNumber: 0,
avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)),
created: DateUtils.currentDBTime(),
created: DateUtils.getDBTime(),
shouldShow: true,
},
};
Expand Down
8 changes: 4 additions & 4 deletions src/libs/SidebarUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ function getOrderedReportIDs(reportIDFromRoute) {
// 2. Outstanding IOUs - Always sorted by iouReportAmount with the largest amounts at the top of the group
// 3. Drafts - Always sorted by reportDisplayName
// 4. Non-archived reports
// - Sorted by lastMessageTimestamp in default (most recent) view mode
// - Sorted by lastActionCreated in default (most recent) view mode
// - Sorted by reportDisplayName in GSD (focus) view mode
// 5. Archived reports
// - Sorted by lastMessageTimestamp in default (most recent) view mode
// - Sorted by lastActionCreated in default (most recent) view mode
// - Sorted by reportDisplayName in GSD (focus) view mode
let pinnedReports = [];
let outstandingIOUReports = [];
Expand Down Expand Up @@ -153,8 +153,8 @@ function getOrderedReportIDs(reportIDFromRoute) {
pinnedReports = _.sortBy(pinnedReports, report => report.displayName.toLowerCase());
outstandingIOUReports = _.sortBy(outstandingIOUReports, 'iouReportAmount').reverse();
draftReports = _.sortBy(draftReports, report => report.displayName.toLowerCase());
nonArchivedReports = _.sortBy(nonArchivedReports, report => (isInDefaultMode ? report.lastMessageTimestamp : report.displayName.toLowerCase()));
archivedReports = _.sortBy(archivedReports, report => (isInDefaultMode ? report.lastMessageTimestamp : report.displayName.toLowerCase()));
nonArchivedReports = _.sortBy(nonArchivedReports, report => (isInDefaultMode ? report.lastActionCreated : report.displayName.toLowerCase()));
archivedReports = _.sortBy(archivedReports, report => (isInDefaultMode ? report.lastActionCreated : report.displayName.toLowerCase()));

// For archived and non-archived reports, ensure that most recent reports are at the top by reversing the order of the arrays because underscore will only sort them in ascending order
if (isInDefaultMode) {
Expand Down
7 changes: 3 additions & 4 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ function getParticipantEmailsFromReport({sharedReportList, reportNameValuePairs,
* @returns {Object}
*/
function getSimplifiedReportObject(report) {
const createTimestamp = lodashGet(report, 'lastActionCreated', 0);
const lastMessageTimestamp = moment.utc(createTimestamp).unix();
const lastActionCreated = lodashGet(report, 'lastActionCreated', 0);
const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], '');
const isLastMessageAttachment = new RegExp(`<img|a\\s[^>]*${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}\\s*=\\s*"[^"]*"[^>]*>`, 'gi').test(lastActionMessage);
const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], '');
Expand Down Expand Up @@ -135,7 +134,7 @@ function getSimplifiedReportObject(report) {
'timestamp',
], 0),
lastReadSequenceNumber,
lastMessageTimestamp,
lastActionCreated,
lastMessageText: isLastMessageAttachment ? '[Attachment]' : lastMessageText,
lastActorEmail,
notificationPreference,
Expand Down Expand Up @@ -414,7 +413,7 @@ function addActions(reportID, text = '', file) {
// Update the report in Onyx to have the new sequence number
const optimisticReport = {
maxSequenceNumber: newSequenceNumber,
lastMessageTimestamp: Date.now(),
lastActionCreated: DateUtils.getDBTime(),
lastMessageText: ReportUtils.formatReportLastMessageText(lastAction.message[0].text),
lastActorEmail: currentUserEmail,
lastReadSequenceNumber: newSequenceNumber,
Expand Down
46 changes: 46 additions & 0 deletions src/libs/migrations/AddLastActionCreated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import Log from '../Log';
import ONYXKEYS from '../../ONYXKEYS';

/**
* This migration adds lastActionCreated to all reports in Onyx, using the value of lastMessageTimestamp
*
* @returns {Promise}
*/
export default function () {
return new Promise((resolve) => {
const connectionID = Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallbacks: true,
callback: (allReports) => {
Onyx.disconnect(connectionID);
const reportsToUpdate = {};
_.each(allReports, (report, key) => {
if (_.has(report, 'lastActionCreated')) {
return;
}

if (!_.has(report, 'lastMessageTimestamp')) {
return;
}

reportsToUpdate[key] = report;
reportsToUpdate[key].lastActionCreated = new Date(report.lastMessageTimestamp)
.toISOString()
.replace('T', ' ')
.replace('Z', '');
});

if (_.isEmpty(reportsToUpdate)) {
Log.info('[Migrate Onyx] Skipped migration AddLastActionCreated');
} else {
Log.info(`[Migrate Onyx] Adding lastActionCreated field to ${_.keys(reportsToUpdate).length} reports`);
// eslint-disable-next-line rulesdir/prefer-actions-set-data
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate);
}
},
});
return resolve();
});
}
2 changes: 1 addition & 1 deletion src/pages/home/sidebar/SidebarLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ const reportSelector = report => report && ({
maxSequenceNumber: report.maxSequenceNumber,
lastReadSequenceNumber: report.lastReadSequenceNumber,
lastMessageText: report.lastMessageText,
lastMessageTimestamp: report.lastMessageTimestamp,
lastActionCreated: report.lastActionCreated,
iouReportID: report.iouReportID,
hasOutstandingIOU: report.hasOutstandingIOU,
statusNum: report.statusNum,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/reportPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default PropTypes.shape({
lastMessageText: PropTypes.string,

/** The time of the last message on the report */
lastMessageTimestamp: PropTypes.number,
lastActionCreated: PropTypes.string,

/** The sequence number of the last action read by the user */
lastReadSequenceNumber: PropTypes.number,
Expand Down
10 changes: 5 additions & 5 deletions tests/actions/ReportTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('actions/Report', () => {
reportID: REPORT_ID,
maxSequenceNumber: 1,
notificationPreference: 'always',
lastMessageTimestamp: 0,
lastActionCreated: '2022-11-22 03:48:27.267',
lastMessageText: 'Testing a comment',
lastActorEmail: TEST_USER_LOGIN,
},
Expand Down Expand Up @@ -240,7 +240,7 @@ describe('actions/Report', () => {
reportID: REPORT_ID,
maxSequenceNumber: 1,
notificationPreference: 'always',
lastMessageTimestamp: 0,
lastActionCreated: '2022-11-22 03:48:27.267',
lastMessageText: 'Comment 1',
lastActorEmail: USER_2_LOGIN,
lastReadSequenceNumber: 0,
Expand All @@ -260,7 +260,7 @@ describe('actions/Report', () => {
person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
sequenceNumber: 1,
shouldShow: true,
created: DateUtils.currentDBTime(),
created: DateUtils.getDBTime(),
},
},
},
Expand Down Expand Up @@ -325,7 +325,7 @@ describe('actions/Report', () => {
avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
shouldShow: true,
created: DateUtils.currentDBTime(),
created: DateUtils.getDBTime(),
reportActionID: 'derp',
};

Expand All @@ -338,7 +338,7 @@ describe('actions/Report', () => {
reportID: REPORT_ID,
maxSequenceNumber: 4,
notificationPreference: 'always',
lastMessageTimestamp: 0,
lastActionCreated: '2022-11-22 03:48:27.267',
lastMessageText: 'Current User Comment 3',
lastActorEmail: 'test@test.com',
lastReadSequenceNumber: 4,
Expand Down
7 changes: 4 additions & 3 deletions tests/ui/UnreadIndicatorsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as NumberUtils from '../../src/libs/NumberUtils';
import LocalNotification from '../../src/libs/Notification/LocalNotification';
import * as Report from '../../src/libs/actions/Report';
import * as CollectionUtils from '../../src/libs/CollectionUtils';
import DateUtils from '../../src/libs/DateUtils';

jest.mock('../../src/libs/Notification/LocalNotification');

Expand Down Expand Up @@ -133,7 +134,7 @@ function signInAndGetAppWithUnreadChat() {
reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
maxSequenceNumber: 9,
lastReadSequenceNumber: 1,
lastMessageTimestamp: MOMENT_TEN_MINUTES_AGO.utc().valueOf(),
lastActionCreated: DateUtils.getDBTime(MOMENT_TEN_MINUTES_AGO.utc().valueOf()),
lastMessageText: 'Test',
participants: [USER_B_EMAIL],
});
Expand Down Expand Up @@ -268,7 +269,7 @@ describe('Unread Indicators', () => {
reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
maxSequenceNumber: 1,
lastReadSequenceNumber: 0,
lastMessageTimestamp: NEW_REPORT_CREATED_MOMENT.utc().valueOf(),
lastActionCreated: DateUtils.getDBTime(NEW_REPORT_CREATED_MOMENT.utc().valueOf()),
lastMessageText: 'Comment 1',
participants: [USER_C_EMAIL],
});
Expand Down Expand Up @@ -497,7 +498,7 @@ describe('Unread Indicators', () => {
delete lastReportAction[lastReportAction.clientID];
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
lastMessageText: lastReportAction.message[0].text,
lastMessageTimestamp: lastReportAction.timestamp,
lastActionCreated: DateUtils.getDBTime(lastReportAction.timestamp),
lastActorEmail: lastReportAction.actorEmail,
maxSequenceNumber: lastReportAction.sequenceNumber,
reportID: REPORT_ID,
Expand Down
26 changes: 23 additions & 3 deletions tests/unit/DateUtilsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,28 @@ describe('DateUtils', () => {
expect(DateUtils.datetimeToRelative(LOCALE, anHourAgo)).toBe('an hour ago');
});

it('should return the date in the format expected by the database when calling currentDBTime', () => {
const currentDBTime = DateUtils.currentDBTime();
expect(currentDBTime).toBe(moment(currentDBTime).format('YYYY-MM-DD HH:mm:ss.SSS'));
describe('getDBTime', () => {
it('should return the date in the format expected by the database', () => {
const getDBTime = DateUtils.getDBTime();
expect(getDBTime).toBe(moment(getDBTime).format('YYYY-MM-DD HH:mm:ss.SSS'));
});

it('should represent the correct moment in utc when used with a standard datetime string', () => {
const timestamp = 'Mon Nov 21 2022 19:04:14 GMT-0800 (Pacific Standard Time)';
const getDBTime = DateUtils.getDBTime(timestamp);
expect(getDBTime).toBe('2022-11-22 03:04:14.000');
});

it('should represent the correct moment in time when used with an ISO string', () => {
const timestamp = '2022-11-22T03:08:04.326Z';
const getDBTime = DateUtils.getDBTime(timestamp);
expect(getDBTime).toBe('2022-11-22 03:08:04.326');
});

it('should represent the correct moment in time when used with a unix timestamp', () => {
const timestamp = 1669086850792;
const getDBTime = DateUtils.getDBTime(timestamp);
expect(getDBTime).toBe('2022-11-22 03:14:10.792');
});
});
});
Loading