Open m-natarajan opened 2 months ago
Current assignee @mallenexpensify is eligible for the Bug assigner, not assigning anyone new.
This has been labelled "Needs Reproduction". Follow the steps here: https://stackoverflowteams.com/c/expensify/questions/16989
@mallenexpensify Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
Need to try to repro. Also should check to see how supportal into NewDot works cuz saw Expensify
as a workspace option there
@mallenexpensify Whoops! This issue is 2 days overdue. Let's get this updated quick!
@mallenexpensify this issue was created 2 weeks ago. Are we close to a solution? Let's make sure we're treating this as a top priority. Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!
Job added to Upwork: https://www.upwork.com/jobs/~021838371009986053911
Triggered auto assignment to Contributor-plus team member for initial proposal review - @getusha (External
)
Updated the OP to include this for the deliverable
Provide reliable reproduction steps and a video for the below bug
@mallenexpensify This is happening when the ongoing request was waiting to be flushed and we trigger sign out flow.
Steps where I was able to repro this -
https://github.com/user-attachments/assets/17e0cac0-e2c0-405f-ae1a-29f16a7566f9
See screenshot below for the residual data on logout
Response of BE which matches with residual data (if we combine with successData provided in API)
{
"jsonCode": 200,
"requestID": "8c883d48df438ad5-DEL",
"onyxData": [
{
"key": "report_6652536854802253",
"onyxMethod": "merge",
"value": {
"participants": {
"16991175": {
"hidden": false,
"notificationPreference": "always"
}
}
}
},
{
"key": "report_6652536854802253",
"onyxMethod": "merge",
"value": {
"chatType": "",
"description": "",
"lastActorAccountID": 16991175,
"lastMessageText": "Test",
"lastVisibleActionCreated": "2024-09-25 04:23:32.197",
"managerID": null,
"ownerAccountID": 0,
"parentReportActionID": "287587431522508935",
"parentReportID": "2758613887018111",
"policyID": "_FAKE_",
"reportID": "6652536854802253",
"reportName": "Chat Report",
"state": "OPEN",
"stateNum": 0,
"statusNum": 0,
"type": "chat",
"visibility": null
}
},
{
"key": "reportActions_6652536854802253",
"onyxMethod": "merge",
"shouldShowPushNotification": true,
"value": {
"1743823375274647167": {
"actionName": "ADDCOMMENT",
"actorAccountID": 16991175,
"avatar": "https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_16.png",
"created": "2024-09-25 04:23:32.197",
"lastModified": "2024-09-25 04:23:32.197",
"message": [
{
"html": "Test",
"text": "Test",
"type": "COMMENT",
"whisperedTo": []
}
],
"originalMessage": {
"html": "Test",
"lastModified": "2024-09-25 04:23:32.197"
},
"person": [
{
"style": "strong",
"text": "Sh",
"type": "TEXT"
}
],
"reportActionID": "1743823375274647167",
"shouldShow": true
}
}
},
{
"key": "personalDetailsList",
"onyxMethod": "merge",
"value": {
"16991175": {
"accountID": 16991175,
"avatar": "https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_16.png",
"displayName": "Sh",
"firstName": "Sh",
"lastName": "",
"login": "work.sa1206+sdm8@gmail.com",
"phoneNumber": "",
"pronouns": "",
"status": null,
"timezone": {
"automatic": true,
"selected": "Asia/Calcutta"
},
"validated": true
}
}
},
{
"key": "reportActions_2758613887018111",
"onyxMethod": "merge",
"value": {
"287587431522508935": {
"childCommenterCount": 1,
"childLastActorAccountID": null,
"childLastMoneyRequestComment": null,
"childLastReceiptTransactionIDs": null,
"childLastVisibleActionCreated": "2024-09-25 04:23:32.197",
"childMoneyRequestCount": null,
"childOldestFourAccountIDs": "16991175",
"childRecentReceiptTransactionIDs": null,
"childReportNotificationPreference": "always",
"childStateNum": 0,
"childStatusNum": 0,
"childType": "chat",
"childVisibleActionCount": 2
}
}
},
{
"key": "report_6652536854802253",
"onyxMethod": "merge",
"value": {
"lastReadTime": "2024-09-25 04:23:32.197"
}
}
],
"previousUpdateID": 2010501382,
"lastUpdateID": 2022079760
}
@mallenexpensify, @getusha Whoops! This issue is 2 days overdue. Let's get this updated quick!
More discussion here in #expensify-open-source
@shubham1206agra are the repro steps specific to data being stuck in BE that might show in LHN? (and/or in search)?
@getusha can you add 🎀 to assign an engineer to review (with the deliverable from them to review the steps > confirm the steps work to reproduce > confirm it's back vs front end and that's it's a bug that can be fixed).
I was able to see reports from the previous account in the Search page following https://github.com/Expensify/App/issues/48427#issuecomment-2372896788 @shubham1206agra is there any way to cancel pending API calls while logging out? I don't see how this could be fixed from the BE. 🎀 👀 🎀
Triggered auto assignment to @madmax330, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
@madmax330 @mallenexpensify @getusha this issue is now 4 weeks old, please consider:
Thanks!
@shubham1206agra 👀 above when you have a min
@shubham1206agra is there any way to cancel pending API calls while logging out? I don't see how this could be fixed from the BE.
I am seeing there is an abort call when unmounting the authscreen https://github.com/Expensify/App/blob/7dde09e01e77ba55a79d043847b7ff86c5c1d1fd/src/libs/actions/Session/index.ts#L756
I think we need to somehow trigger the Onyx.clear after all the Onyx.update flushes.
Is there a proposal for this or do we think it's a backend issue?
Is there a proposal for this or do we think it's a backend issue?
I gave the repro steps, but this is very much open for external contributors.
@mallenexpensify I think we should update the reproduction steps and post it in slack to see if we could get proposals.
QQ - I have this as the actual result in the OP now for the bug
Old chats from Account A in LHN (when click on them get 404'd)
@shubham1206agra , do your repro steps lead to the bug above? If not, can you provide the actual behaviour?
@madmax330 , hang on a min, I'll ping ya soon
@madmax330, @mallenexpensify, @getusha Eep! 4 days overdue now. Issues have feelings too...
@shubham1206agra 👀 above plz, thx
New steps
Expected Result - No report that is not associated with second account should not be shown in search results.
Upwork job price has been updated to $500
Thx @shubham1206agra , I updated the title and the OP and bumped the price to $500 to get 👀.
@shubham1206agra can you please accept the job and reply here once you have? https://www.upwork.com/jobs/~021838371009986053911
@mallenexpensify Accepted offer
Edited by proposal-police: This proposal was edited at 2024-10-13 23:49:51 UTC.
Logging out immediately after commenting on a report, while the request is still being processed, results in the report being shown after logging into a different account.
For CONST.API_REQUEST_TYPE.WRITE
operations, response data is not applied immediately but queued until all pending requests are completed
When a user signs out, all session-specific data is removed from the Onyx store. However, the same is not done for queued updates, which leads to stale data being applied in the new session.
We can add the following function to QueuedOnyxUpdates.ts
function clear(keysToPreserve?: OnyxKey[]) {
queuedOnyxUpdates = keysToPreserve
? queuedOnyxUpdates.filter((onyxUpdate) => keysToPreserve.includes(onyxUpdate.key as OnyxKey))
: [];
}
This function filters queued updates based on the keys we want to preserve.
Next, call it from cleanupSession()
...
HttpUtils.cancelPendingRequests();
PersistedRequests.clear();
+ QueuedOnyxUpdates.clear();
NetworkConnection.clearReconnectionCallbacks();
SessionUtils.resetDidUserLogInDuringSession();
...
This ensures that any queued updates from the previous session are cleared upon logout, preventing outdated data from being applied in the next session.
A similar bug exists when switching between delegate (aka copilot) accounts. In this case we'll have to filter out all data except KEYS_TO_PRESERVE_DELEGATE_ACCESS
here and here.
.then(() => {
+ QueuedOnyxUpdates.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS);
Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS)
})
Instead of manually clearing the updates array, we can store it in Onyx, which will handle it automatically. This approach is more future-proof as we won't need to remember to call the clear()
function. This will also fix an issue where the queued updates are lost after reloading the page.
Updated QueuedOnyxUpdates.ts will be as follows:
import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import ONYXKEYS from "@src/ONYXKEYS";
let queuedOnyxUpdates: OnyxUpdate[] = [];
Onyx.connect({
key: ONYXKEYS.ONYX_UPDATE_QUEUE,
callback: (val) => {
queuedOnyxUpdates = val ?? [];
},
});
function queueOnyxUpdates(updates: OnyxUpdate[]): Promise<void> {
queuedOnyxUpdates = queuedOnyxUpdates.concat(updates);
return Onyx.set(ONYXKEYS.ONYX_UPDATE_QUEUE, queuedOnyxUpdates);
}
function flushQueue(): Promise<void> {
const updates = queuedOnyxUpdates.concat({
key: ONYXKEYS.ONYX_UPDATE_QUEUE,
onyxMethod: Onyx.METHOD.SET,
value: null
});
return Onyx.update(updates).then(() => {
queuedOnyxUpdates = [];
});
}
export {queueOnyxUpdates, flushQueue};
Then the ONYX_UPDATE_QUEUE
key will need to be added to the ONYXKEYS.ts
file.
All changes: https://github.com/CyberAndrii/ExpensifyApp/commit/858bda6e67352b80f1682dface22c75cb3f33710
Contributor: @shubham1206agra paid $250 via Upwork for reproducible steps.
@getusha 👀 on the above proposal plz. Thx.
Will review this first thing tomorrow
Edited by proposal-police: This proposal was edited at 2024-10-21 06:51:23 UTC.
After signing out of an account and signing into a different account, cached data remains when searching
PersistedRequests
will be cleared, and flushOnyxUpdatesQueue
will be executed because we no longer have any requests persisted.
https://github.com/Expensify/App/blob/cc0d741dba3e69911ef929b630e8bb1bf0d28fcb/src/libs/actions/Session/index.ts#L757
https://github.com/Expensify/App/blob/cc0d741dba3e69911ef929b630e8bb1bf0d28fcb/src/libs/Network/SequentialQueue.ts#L173-L175To resolve this issue, We should clear the queuedOnyxUpdates
data when logout is called, as we don't need to continue storing it since it is no longer used. The code change will be like this:
//.src/libs/actions/QueuedOnyxUpdates.ts#L23
+ const clearQueueOnyxUpdates = () => {
+ queuedOnyxUpdates = [];
+ };
src/libs/Network/SequentialQueue.ts#L207
function push(newRequest: OnyxRequest) {
const {checkAndFixConflictingRequest} = newRequest;
if (checkAndFixConflictingRequest) {
const requests = PersistedRequests.getAll();
const {conflictAction} = checkAndFixConflictingRequest(requests);
Log.info(`[SequentialQueue] Conflict action for command ${newRequest.command} - ${conflictAction.type}:`);
// don't try to serialize a function.
// eslint-disable-next-line no-param-reassign
delete newRequest.checkAndFixConflictingRequest;
if (conflictAction.type === 'push') {
//.src/libs/Network/SequentialQueue.ts#L220
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
PersistedRequests.save(newRequest);
} else if (conflictAction.type === 'replace') {
//.src/libs/Network/SequentialQueue.ts#L222
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
PersistedRequests.update(conflictAction.index, newRequest);
} else {
Log.info(`[SequentialQueue] No action performed to command ${newRequest.command} and it will be ignored.`);
}
} else {
//.src/libs/Network/SequentialQueue.ts#L227
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
// Add request to Persisted Requests so that it can be retried if it fails
PersistedRequests.save(newRequest);
}
// If we are offline we don't need to trigger the queue to empty as it will happen when we come back online
if (NetworkStore.isOffline()) {
return;
}
// If the queue is running this request will run once it has finished processing the current batch
if (isSequentialQueueRunning) {
isReadyPromise.then(flush);
return;
}
flush();
}
Or we need to check whether the session exists or not to update queuedOnyxUpdates
to Onyx
. This means, if the session
does not exist, we can update the preserved keys. If the session
exists, we can update all keys. Something like this:
//.src/libs/actions/QueuedOnyxUpdates.ts#L8
+ let currentAccountID: number | undefined;
+ Onyx.connect({
+ key: ONYXKEYS.SESSION,
+ callback: (session) => {
+ currentAccountID = session?.accountID;
+ },
+ });
//.src/libs/actions/QueuedOnyxUpdates.ts#L24
function flushQueue(): Promise<void> {
+ if (!currentAccountID) {
+ const preservedKeys: OnyxKey[] = [
+ ONYXKEYS.NVP_TRY_FOCUS_MODE,
+ ONYXKEYS.PREFERRED_THEME,
+ ONYXKEYS.NVP_PREFERRED_LOCALE,
+ ONYXKEYS.SESSION,
+ ONYXKEYS.IS_LOADING_APP,
+ ONYXKEYS.CREDENTIALS,
+ ONYXKEYS.IS_SIDEBAR_LOADED,
+ ];
+ queuedOnyxUpdates = queuedOnyxUpdates.filter((update) => preservedKeys.includes(update.key as OnyxKey));
+ }
return Onyx.update(queuedOnyxUpdates).then(() => {
queuedOnyxUpdates = [];
});
}
Can't seem to reproduce the issue anymore, the request is cancelled the moment i log out.
https://github.com/user-attachments/assets/f08d12f4-d302-4ab9-9f6d-f928c44b59b8
@getusha , I'm still able to reproduce this issue. Can you send a text message in offline mode, switch to online mode, and then quickly log out? like video bellow:
I'm still able to reproduce this issue. Can you send a text message in offline mode, switch to online mode, and then quickly log out? like video bellow:
that's what i am doing @huult
do i need to send that many messages?
@getusha It depends on the time between switching to online mode and logging out. If you complete the persistent request before logging out, the issue won't occur. However, if the persistent request isn't completed in time, the issue will occur.
@madmax330, @mallenexpensify, @getusha Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
@getusha It depends on the time between switching to online mode and logging out. If you complete the persistent request before logging out, the issue won't occur. However, if the persistent request isn't completed in time, the issue will occur.
The request isn't completed in time but it's cancelled after logging out.
The request isn't completed in time but it's cancelled after logging out.
Yes. Before the PersistedRequests were canceled, some APIs had responded, and their responses were set to the queue. After we canceled PersistedRequests, the data in the queue will be updated to local storage. Therefore, the data in the queue will still exist after logout, leading to this issue.
So, we need to clear the data in the queue when calling logout, and we don’t need to store that data anymore because it will not be used after logout.
@madmax330, @mallenexpensify, @getusha Huh... This is 4 days overdue. Who can take care of this?
@madmax330, @mallenexpensify, @getusha Still overdue 6 days?! Let's take care of this!
I managed to reproduce it,
@huult flushQueue
appears to be doing the same thing.
I applied your solution and here is the result:
https://github.com/user-attachments/assets/cc64a9bf-9c83-4de2-ae54-e162cfed073b
index 35c7b2bf779..e95a0407e5d 100644
--- a/src/libs/Network/SequentialQueue.ts
+++ b/src/libs/Network/SequentialQueue.ts
@@ -9,6 +9,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type OnyxRequest from '@src/types/onyx/Request';
import * as NetworkStore from './NetworkStore';
+import { WRITE_COMMANDS } from "@libs/API/types";
type RequestError = Error & {
name?: string;
@@ -216,6 +217,10 @@ function push(newRequest: OnyxRequest) {
// eslint-disable-next-line no-param-reassign
delete newRequest.checkAndFixConflictingRequest;
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
+
if (conflictAction.type === 'push') {
PersistedRequests.save(newRequest);
} else if (conflictAction.type === 'replace') {
@getusha Can you move your code from L218 to L227, or do it like my code example?
//.src/libs/Network/SequentialQueue.ts#L207
function push(newRequest: OnyxRequest) {
const {checkAndFixConflictingRequest} = newRequest;
if (checkAndFixConflictingRequest) {
const requests = PersistedRequests.getAll();
const {conflictAction} = checkAndFixConflictingRequest(requests);
Log.info(`[SequentialQueue] Conflict action for command ${newRequest.command} - ${conflictAction.type}:`);
// don't try to serialize a function.
// eslint-disable-next-line no-param-reassign
delete newRequest.checkAndFixConflictingRequest;
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
if (conflictAction.type === 'push') {
PersistedRequests.save(newRequest);
} else if (conflictAction.type === 'replace') {
PersistedRequests.update(conflictAction.index, newRequest);
} else {
Log.info(`[SequentialQueue] No action performed to command ${newRequest.command} and it will be ignored.`);
}
} else {
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
// Add request to Persisted Requests so that it can be retried if it fails
PersistedRequests.save(newRequest);
}
// If we are offline we don't need to trigger the queue to empty as it will happen when we come back online
if (NetworkStore.isOffline()) {
return;
}
// If the queue is running this request will run once it has finished processing the current batch
if (isSequentialQueueRunning) {
isReadyPromise.then(flush);
return;
}
flush();
}
@getusha I updated the comment! Can you check my comment above? If you can still reproduce the issue, I will share it with the test branch.
@huult will that make a difference? here i applied this and still can reproduce it
https://github.com/user-attachments/assets/8e7229df-13e4-44f4-9a0e-d251ecb386ea
index 35c7b2bf779..c804d0b9022 100644
--- a/src/libs/Network/SequentialQueue.ts
+++ b/src/libs/Network/SequentialQueue.ts
@@ -9,6 +9,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type OnyxRequest from '@src/types/onyx/Request';
import * as NetworkStore from './NetworkStore';
+import { WRITE_COMMANDS } from "@libs/API/types";
type RequestError = Error & {
name?: string;
@@ -216,12 +217,19 @@ function push(newRequest: OnyxRequest) {
// eslint-disable-next-line no-param-reassign
delete newRequest.checkAndFixConflictingRequest;
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
+
if (conflictAction.type === 'push') {
PersistedRequests.save(newRequest);
} else if (conflictAction.type === 'replace') {
PersistedRequests.update(conflictAction.index, newRequest);
} else {
Log.info(`[SequentialQueue] No action performed to command ${newRequest.command} and it will be ignored.`);
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
}
} else {
// Add request to Persisted Requests so that it can be retried if it fails
@getusha You added the wrong code change. Sorry, I forgot to add the line to change in my code example. it's should be:
function push(newRequest: OnyxRequest) {
const {checkAndFixConflictingRequest} = newRequest;
if (checkAndFixConflictingRequest) {
const requests = PersistedRequests.getAll();
const {conflictAction} = checkAndFixConflictingRequest(requests);
Log.info(`[SequentialQueue] Conflict action for command ${newRequest.command} - ${conflictAction.type}:`);
// don't try to serialize a function.
// eslint-disable-next-line no-param-reassign
delete newRequest.checkAndFixConflictingRequest;
if (conflictAction.type === 'push') {
//.src/libs/Network/SequentialQueue.ts#L220
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
PersistedRequests.save(newRequest);
} else if (conflictAction.type === 'replace') {
//.src/libs/Network/SequentialQueue.ts#L222
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
PersistedRequests.update(conflictAction.index, newRequest);
} else {
Log.info(`[SequentialQueue] No action performed to command ${newRequest.command} and it will be ignored.`);
}
} else {
//.src/libs/Network/SequentialQueue.ts#L227
+ if (newRequest.command === WRITE_COMMANDS.LOG_OUT) {
+ QueuedOnyxUpdates.clearQueueOnyxUpdates();
+ }
// Add request to Persisted Requests so that it can be retried if it fails
PersistedRequests.save(newRequest);
}
// If we are offline we don't need to trigger the queue to empty as it will happen when we come back online
if (NetworkStore.isOffline()) {
return;
}
// If the queue is running this request will run once it has finished processing the current batch
if (isSequentialQueueRunning) {
isReadyPromise.then(flush);
return;
}
flush();
}
@huult i don't see how that makes any difference, we are adding the same code in each condition which is the same as adding it outside the condition
@getusha The difference lies in the else condition; please add clearQueueOnyxUpdates
data after the else of if (checkAndFixConflictingRequest) {
@getusha here is test branch . Sorry, I added a code example that is not good.
If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
Version Number: Reproducible in staging?: Needs Reproduction Reproducible in production?: Needs Reproduction If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by: @mallenexpensify Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1725058768402589
Deliverable
Provide reliable reproduction steps and a video for the below bug
Action Performed:
Expected Result:
No report that is not associated with second account should not be shown in search results.
Actual Result:
Old chats from Account A in show in search.
Workaround:
Unknown
Platforms:
Which of our officially supported platforms is this issue occurring on?
Screenshots/Videos
Add any screenshot/video evidence
View all open jobs on GitHub
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @getusha