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

[$250] Track expense - App crashes on report details RHP after submitting track expense to workspace #53547

Open
8 tasks done
IuliiaHerets opened this issue Dec 4, 2024 · 31 comments
Assignees
Labels
Bug Something is broken. Auto assigns a BugZero manager. External Added to denote the issue can be worked on by a contributor Help Wanted Apply this label when an issue is open to proposals by contributors Internal Requires API changes or must be handled by Expensify staff Reviewing Has a PR in review Weekly KSv2

Comments

@IuliiaHerets
Copy link

IuliiaHerets commented Dec 4, 2024

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 9.0.71-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught on HybridApp, is this reproducible on New Expensify Standalone?: Yes
If this was caught during regression testing, add the test name, ID and link from TestRail: Exp https://expensify.testrail.io/index.php?/tests/view/5299012
Email or phone of affected tester (no customers): [email protected]
Issue reported by: Applause Internal Team

Action Performed:

Precondition:

  • Account has a workspace.
  • Workspace has no unsettled expenses.
  1. Go to staging.new.expensify.com
  2. Go offline.
  3. Go to self DM.
  4. Track a manual expense.
  5. Click Categorize it from the actionable whisper.
  6. Select a category, enter merchant and submit the expense.
  7. Go to transaction thread in the workspace chat.
  8. Click on the report header.
  9. Go online while staying on the report details RHP.

Expected Result:

App will not crash.

Actual Result:

App crashes.
Tester's devices where the issue occurs:
macOS Ventura 13.4 - Chrome
Samsung Galaxy Z Fold4 - Android 14,
Apple iPhone 15 Pro Max - iOS 18.1 Beta.

Workaround:

Unknown

Platforms:

  • Android: Standalone
  • Android: HybridApp
  • Android: mWeb Chrome
  • iOS: Standalone
  • iOS: HybridApp
  • iOS: mWeb Safari
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

Bug6684140_1733307605979.20241204_181332.mp4

Bug6684140_1733307605993!staging.new.expensify.com-1733307405873.txt

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021864495667152345213
  • Upwork Job ID: 1864495667152345213
  • Last Price Increase: 2024-12-05
Issue OwnerCurrent Issue Owner: @hoangzinh
@IuliiaHerets IuliiaHerets added Daily KSv2 Bug Something is broken. Auto assigns a BugZero manager. labels Dec 4, 2024
Copy link

melvin-bot bot commented Dec 4, 2024

Triggered auto assignment to @adelekennedy (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

@mkzie2
Copy link
Contributor

mkzie2 commented Dec 4, 2024

Edited by proposal-police: This proposal was edited at 2024-12-05 16:22:42 UTC.

Proposal

Please re-state the problem that we are trying to solve in this issue.

Track expense - App crashes on report details RHP after submitting track expense to workspace

What is the root cause of that problem?

After we categorize a track expense, the backend returns the correct parentReportID of the expense report.

But if we offline and online, ReconnectApp API is called which returns parentReportID as null in the last onyx data of the expense report

Screen.Recording.2024-12-05.at.23.19.31.mov

The parentReportID changes to an empty string which causes the crash app when we use useOnyx here

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`);

What changes do you think we should make in order to solve the problem?

Fallback the parentReportID to -1 here to prevent the crash app

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID || '-1'}`);

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`);

What specific scenarios should we cover in automated tests to prevent reintroducing this issue in the future?

What alternative solutions did you explore? (Optional)

Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.

@saifelance
Copy link

Please re-state the problem that we are trying to solve in this issue.

The app crashes on the report details Right-Hand Panel (RHP) after submitting a tracked expense to a workspace offline and going online, disrupting user access to the report.

What is the root cause of that problem?

  1. Offline-to-Online Transition: State changes during this transition are not handled, causing invalid or incomplete data rendering.
  2. Undefined Data: Missing or incomplete report or parentAction objects cause crashes when accessed.

What changes do you think we should make in order to solve the problem?

src/components/ParentNavigationSubtitle.tsx

function ParentNavigationSubtitle({
    parentNavigationSubtitleData,
    parentReportActionID,
    parentReportID = '',
    pressableStyles,
}: ParentNavigationSubtitleProps) {
    const styles = useThemeStyles();
    const { workspaceName, reportName } = parentNavigationSubtitleData;
    const { isOffline } = useNetwork();
    const { translate } = useLocalize();
    const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`);
    const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report);

    // Guard clause for missing report name
    if (!reportName) {
        return;
    }

    // Validate report and parentAction
    if (!report || !reportName) {
        console.error("Missing report data", { parentReportID });
        return;
    }

    const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1');
    if (!parentAction) {
        console.error("Missing parent action data", { parentReportID, parentReportActionID });
        return;
    }

    // Prevent navigation if offline
    if (isOffline) {
        console.warn("Cannot navigate while offline");
        return;
    }

    return (
        <PressableWithoutFeedback
            onPress={() => {
                const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(
                    parentAction, parentAction?.reportActionID ?? '-1', canUserPerformWriteAction
                );

                // Pop the thread report screen before navigating to the chat report.
                Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID));

                if (isVisibleAction) {
                    // Pop the chat report screen before navigating to the linked report action.
                    Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID, parentReportActionID), true);
                } else {
                    console.warn("Report action is not visible");
                }
            }}
            accessibilityLabel={translate('threads.parentNavigationSummary', { reportName, workspaceName })}
            role={CONST.ROLE.LINK}
            style={pressableStyles}
        >
            <Text
                style={[styles.optionAlternateText, styles.textLabelSupporting]}
                numberOfLines={1}
            >
                {!!reportName && (
                    <>
                        <Text style={[styles.optionAlternateText, styles.textLabelSupporting]}>{`${translate('threads.from')} `}</Text>
                        <Text style={[styles.optionAlternateText, styles.textLabelSupporting, styles.link]}>{reportName}</Text>
                    </>
                )}
                {!!workspaceName && <Text style={[styles.optionAlternateText, styles.textLabelSupporting]}>{` ${translate('threads.in')} ${workspaceName}`}</Text>}
            </Text>
        </PressableWithoutFeedback>
    );
}
  • Null/Undefined checks added for report, parentAction, parentReportID, and parentReportActionID.
  • Offline state checks to prevent navigation when offline.
  • Early returns if data is invalid, preventing further execution and potential crashes.

What alternative solutions did you explore? (Optional)

  1. Defer Navigation: Wait until all data is loaded before rendering or navigating.
  2. Server-Side Validation: Use server-side checks to mitigate client-side data issues.
  3. Error Boundary: Implement an error boundary to catch crashes and prevent app failure.

@adelekennedy adelekennedy added the External Added to denote the issue can be worked on by a contributor label Dec 5, 2024
@melvin-bot melvin-bot bot changed the title Track expense - App crashes on report details RHP after submitting track expense to workspace [$250] Track expense - App crashes on report details RHP after submitting track expense to workspace Dec 5, 2024
Copy link

melvin-bot bot commented Dec 5, 2024

Job added to Upwork: https://www.upwork.com/jobs/~021864495667152345213

@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Dec 5, 2024
Copy link

melvin-bot bot commented Dec 5, 2024

Triggered auto assignment to Contributor-plus team member for initial proposal review - @hoangzinh (External)

@adelekennedy
Copy link

reproduced

@hoangzinh
Copy link
Contributor

@mkzie2 can you elaborate on why it only happens when network is offline?

@saifelance
Copy link

Job added to Upwork: https://www.upwork.com/jobs/~021864495667152345213

Send invite please also previously are pending?

@mkzie2
Copy link
Contributor

mkzie2 commented Dec 5, 2024

@hoangzinh We have a problem from the backend, after we go offline and online, the CategorizeTrackedExpense API returns parentReportID and parentReportActionID of the expense report is null. This causes parentReportID in ParentNavigationSubtitle to change from an existing ID to an empty string.

@saifelance
Copy link

@mkzie2 @hoangzinh

Solution:

Backend Fix:

Ensure parentReportID and parentReportActionID are always populated in the API response after offline-to-online transitions. Add validation and retry mechanisms.

Frontend Fix:

  • Use the last valid parentReportID/parentReportActionID as fallback if API returns null.
  • Cache values locally during offline mode.
  • Add graceful handling and retry logic to refetch missing data.
  • These steps will ensure stability and consistent data display in ParentNavigationSubtitle.

@hoangzinh
Copy link
Contributor

@mkzie2 But it seems to BE also returns correct parentReportID and parentReportActionID in same batch

Screenshot 2024-12-05 at 22 03 18

@hoangzinh
Copy link
Contributor

@saifelance Based on proposal template here, the RCA needs to be clear and easily understood problem with a "root cause". Please update your proposal with a RCA. Also you can update existing proposal, instead of creating new a one

@saifelance
Copy link

@hoangzinh The backend returns correct IDs most of the time, but during offline-to-online transitions, the state handling is inconsistent, leading to null values for these fields.

@hoangzinh
Copy link
Contributor

the state handling is inconsistent, leading to null values for these fields

@saifelance can you elaborate more on it? If it's possible, can you link to which line of codes are handling state inconsistent? Thank you

@saifelance
Copy link

1. Null/Undefined Check for Report Data

// Check if report is valid before proceeding
if (!report || !reportName) {
    console.error("Missing report data", { parentReportID });
    return;
}

// Check if parentAction is valid
const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1');
if (!parentAction) {
    console.error("Missing parent action data", { parentReportID, parentReportActionID });
    return;
}

2. Offline/Online Network State Handling

// Prevent navigation if offline
if (isOffline) {
    console.warn("Cannot navigate while offline");
    return;
}

// Proceed with navigation if online
const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(
    parentAction, parentAction?.reportActionID ?? '-1', canUserPerformWriteAction
);
if (isVisibleAction) {
    Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID, parentReportActionID), true);
} else {
    console.warn("Report action is not visible");
}

3. Handle Missing Data for parentReportID and parentReportActionID

// Validate parentReportID and parentReportActionID
if (!parentReportID || !parentReportActionID) {
    console.error("Missing required IDs: parentReportID or parentReportActionID");
    return;
}

4. General Safe Data Access

// General check for undefined data
if (!parentNavigationSubtitleData || !parentNavigationSubtitleData.reportName) {
    console.error("Missing subtitle data");
    return;
}

Summary of Changes:

  • Added null/undefined checks for report, parentAction, parentReportID, and parentReportActionID.
  • Added offline checks to block navigation when offline.
  • Implemented early returns for invalid data to avoid crashes.

@hoangzinh Could you please check if this helps?

@mkzie2
Copy link
Contributor

mkzie2 commented Dec 5, 2024

@hoangzinh You're right, the problem comes from ReconnectApp API. The last update has parentReportID is null. I updated my proposal.

Screen.Recording.2024-12-05.at.23.19.31.mov

@saifelance
Copy link

The issue stems from the ReconnectApp API, where the last update intermittently has parentReportID set to null. This causes the ParentNavigationSubtitle to lose its previous valid parentReportID, leading to inconsistent data display in the frontend.

@maddylewis maddylewis moved this to Product (CRITICAL) in [#whatsnext] #retain Dec 5, 2024
@hoangzinh
Copy link
Contributor

Agree with @mkzie2 and @saifelance, the issue comes from ReconnectApp API. We can work around as @mkzie2's suggested, but it causes an issue with navigation back to workspace being hidden.

Screenshot 2024-12-08 at 23 05 03

Therefore it should be fixed in BE. @adelekennedy can you add an internal label for this issue?

@saifelance
Copy link

@hoangzinh The issue with navigation back to the workspace being hidden stems from the ReconnectApp API intermittently returning null or incomplete parentReportID data, affecting frontend behavior. Ensure consistent parentReportID responses in the backend with retry logic or default values.

@saifelance
Copy link

@hoangzinh @adelekennedy

@mkzie2
===>The last update has parentReportID is null. I updated my #53547 (comment).

No its required backend fixing only

The frontend relies on parentReportID and parentReportActionID, but null values from the backend cause navigation issues. Adding a fallback mechanism in the frontend can stabilize behavior temporarily. However, a backend fix is essential for a long-term solution.

@saifelance
Copy link

saifelance commented Dec 9, 2024

@MelvinBot Could you send invite Upwork job https://www.upwork.com/freelancers/saifurrehman?

@mkzie2
Copy link
Contributor

mkzie2 commented Dec 9, 2024

but it causes an issue with navigation back to workspace being hidden.

@hoangzinh I agree this is a backend bug. But we still need a frontend fix as my solution to prevent the app crashes in some cases with the same problem. For example, when we change the value in the debug mode:

Screen.Recording.2024-12-09.at.17.14.14.mov

@saifelance
Copy link

@mkzie2

Here is updated code

import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import type {ParentNavigationSummaryParams} from '@src/languages/types';
import ROUTES from '@src/ROUTES';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import Text from './Text';

type ParentNavigationSubtitleProps = {
    parentNavigationSubtitleData: ParentNavigationSummaryParams;

    /** parent Report ID */
    parentReportID?: string;

    /** parent Report Action ID */
    parentReportActionID?: string;

    /** PressableWithoutFeedack additional styles */
    pressableStyles?: StyleProp<ViewStyle>;
};

import React, {useState, useEffect} from 'react';

function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportActionID, parentReportID = '', pressableStyles}: ParentNavigationSubtitleProps) {
    const styles = useThemeStyles();
    const {workspaceName, reportName} = parentNavigationSubtitleData;
    const {isOffline} = useNetwork();
    const {translate} = useLocalize();
    const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`);
    const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report);

    // State to store last valid IDs
    const [cachedParentReportID, setCachedParentReportID] = useState(parentReportID);
    const [cachedParentReportActionID, setCachedParentReportActionID] = useState(parentReportActionID);

    // Cache valid IDs when they are available
    useEffect(() => {
        if (parentReportID) {
            setCachedParentReportID(parentReportID);
        }
        if (parentReportActionID) {
            setCachedParentReportActionID(parentReportActionID);
        }
    }, [parentReportID, parentReportActionID]);

    // Use cached IDs if the current ones are missing
    const finalParentReportID = parentReportID || cachedParentReportID;
    const finalParentReportActionID = parentReportActionID || cachedParentReportActionID;

    // We should not display the parent navigation subtitle if the user does not have access to the parent chat (the reportName is empty in this case)
    if (!reportName) {
        return null;
    }

    return (
        <PressableWithoutFeedback
            onPress={() => {
                const parentAction = ReportActionsUtils.getReportAction(finalParentReportID, finalParentReportActionID ?? '-1');
                const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1', canUserPerformWriteAction);

                // Pop the thread report screen before navigating to the chat report.
                Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(finalParentReportID));
                if (isVisibleAction && !isOffline) {
                    // Pop the chat report screen before navigating to the linked report action.
                    Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(finalParentReportID, finalParentReportActionID), true);
                }
            }}
            accessibilityLabel={translate('threads.parentNavigationSummary', {reportName, workspaceName})}
            role={CONST.ROLE.LINK}
            style={pressableStyles}
        >
            <Text
                style={[styles.optionAlternateText, styles.textLabelSupporting]}
                numberOfLines={1}
            >
                {!!reportName && (
                    <>
                        <Text style={[styles.optionAlternateText, styles.textLabelSupporting]}>{`${translate('threads.from')} `}</Text>
                        <Text style={[styles.optionAlternateText, styles.textLabelSupporting, styles.link]}>{reportName}</Text>
                    </>
                )}
                {!!workspaceName && <Text style={[styles.optionAlternateText, styles.textLabelSupporting]}>{` ${translate('threads.in')} ${workspaceName}`}</Text>}
            </Text>
        </PressableWithoutFeedback>
    );
}

ParentNavigationSubtitle.displayName = 'ParentNavigationSubtitle';
export default ParentNavigationSubtitle;

Key Changes:

1. Cached IDs:

Added useState to store the last valid parentReportID and parentReportActionID.
useEffect updates these states whenever valid values are provided.

2. Fallback Logic:

Use cached values (cachedParentReportID, cachedParentReportActionID) if the backend returns null or empty values.

3. No Breakage on Null IDs:

Prevents navigation from breaking by ensuring fallback values are always available.

Consider Adding Logs for Debugging:

Add logs to identify when cached values are being used:

console.log('Using cached ID:', {finalParentReportID, finalParentReportActionID});

@hoangzinh
Copy link
Contributor

@MelvinBot Could you send invite Upwork job https://www.upwork.com/freelancers/saifurrehman?

@saifelance please ensure you read this Contributing Guides thoroughly. Basically, you only start to make a PR until you're hired. In order to be hired, one of the important note

Note: Before submitting a proposal on an issue, be sure to read any other existing proposals. ALL NEW PROPOSALS MUST BE DIFFERENT FROM EXISTING PROPOSALS. The difference should be important, meaningful or considerable.

@hoangzinh
Copy link
Contributor

but it causes an issue with navigation back to workspace being hidden.

@hoangzinh I agree this is a backend bug. But we still need a frontend fix as my solution to prevent the app crashes in some cases with the same problem. For example, when we change the value in the debug mode:

Screen.Recording.2024-12-09.at.17.14.14.mov

@mkzie2 agree. Let's see how internal engineer thoughts.

@saifelance
Copy link

@MelvinBot Could you send invite Upwork job https://www.upwork.com/freelancers/saifurrehman?

@saifelance please ensure you read this Contributing Guides thoroughly. Basically, you only start to make a PR until you're hired. In order to be hired, one of the important note

Note: Before submitting a proposal on an issue, be sure to read any other existing proposals. ALL NEW PROPOSALS MUST BE DIFFERENT FROM EXISTING PROPOSALS. The difference should be important, meaningful or considerable.

Of course, I have also sent the updated frontend code changes. Could you please verify them?

@saifelance saifelance mentioned this issue Dec 9, 2024
58 tasks
@melvin-bot melvin-bot bot added Reviewing Has a PR in review Weekly KSv2 and removed Daily KSv2 labels Dec 9, 2024
@hoangzinh
Copy link
Contributor

@saifelance I think we just need to fallback the parentReportID to -1 as @mkzie2 suggested in his proposal is enough if we want to avoid app crash. Btw, just wait internal engineer thoughts on it.

@hoangzinh
Copy link
Contributor

@adelekennedy just in case you missed this comment, it should be an "Internal" issue.

@saifelance
Copy link

@saifelance I think we just need to fallback the parentReportID to -1 as @mkzie2 suggested in his proposal is enough if we want to avoid app crash. Btw, just wait internal engineer thoughts on it.

@hoangzinh If you only need to apply a fallback for parentReportID (and not for parentReportActionID),

@adelekennedy suggestion to fallback to '-1' when parentReportID is null or empty is a reasonable temporary fix. The crash happens because the useOnyx hook expects a valid parentReportID, and null or an empty string causes errors.

The proposed solution (fallback to '-1') is a good temporary fix to prevent app crashes. However, the root cause (backend behavior after reconnecting) should be addressed for a long-term fix.

@mkzie2
Copy link
Contributor

mkzie2 commented Dec 9, 2024

@hoangzinh I think we can add a C+ reviewed comment to get the eye from internal team.

@hoangzinh
Copy link
Contributor

@mkzie2 but I haven't had any proposal to recommend yet, therefore I think we should add an internal label first to look for an internal engineer to work on BE fix, then consider if we want to avoid app crash in FE.

@adelekennedy adelekennedy added the Internal Requires API changes or must be handled by Expensify staff label Dec 9, 2024
@melvin-bot melvin-bot bot added Weekly KSv2 and removed Weekly KSv2 labels Dec 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something is broken. Auto assigns a BugZero manager. External Added to denote the issue can be worked on by a contributor Help Wanted Apply this label when an issue is open to proposals by contributors Internal Requires API changes or must be handled by Expensify staff Reviewing Has a PR in review Weekly KSv2
Projects
Status: Product (CRITICAL)
Development

No branches or pull requests

5 participants