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

Refactor Get_ReportHistory #9613

Merged
merged 43 commits into from
Jul 21, 2022
Merged

Refactor Get_ReportHistory #9613

merged 43 commits into from
Jul 21, 2022

Conversation

sketchydroide
Copy link
Contributor

@sketchydroide sketchydroide commented Jun 28, 2022

CC @marcaaron - let me know if I'm going in the right direction

Details

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/212870

Tests

For this test you need a chat with over 100 actions, if more than 150 even better.
Also before starting make sure you have the latest changes from Web-Expensify pulled

  1. Open the app, and go to the chat with more than 100 actions.
  2. Verify that is opens and you can see the first most recent actions.
  3. Scroll up until you see the loading spinner
  4. Verify that after a while you get the next set of actions
  5. Repeat until you see the start of the chat.
  6. Verify that it work every time.
  • [] Verify that no errors appear in the JS console

PR Review Checklist

Contributor (PR Author) Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • I verified there are no console errors (if there’s a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained “why” the code was doing something instead of only explaining “what” the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by tagging the marketing team on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named “index.js”. All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • Any functional components have the displayName property
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose and it is
  • If a new CSS style is added I verified that:
    • A similar style doesn’t already exist
    • The style can’t be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.

PR Reviewer Checklist

  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • iOS / native
    • Android / native
    • iOS / Safari
    • Android / Chrome
    • MacOS / Chrome
    • MacOS / Desktop
  • I verified there are no console errors (if there’s a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained “why” the code was doing something instead of only explaining “what” the code was doing.
    • I verified any copy / text shown in the product was added in all src/languages/* files
    • I verified any copy / text that was added to the app is correct English and approved by marketing by tagging the marketing team on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named “index.js”. All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • Any functional components have the displayName property
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn’t already exist
    • The style can’t be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.

QA Steps

For this test you need a chat with over 100 actions, if more than 150 even better.

  1. Open the app, and go to the chat with more than 100 actions.
  2. Verify that is opens and you can see the first most recent actions.
  3. Scroll up until you see the loading spinner
  4. Verify that after a while you get the next set of actions
  5. Repeat until you see the start of the chat.
  6. Verify that it work every time.
  • Verify that no errors appear in the JS console

Screenshots

Web

Mobile Web

Desktop

iOS

Android

@sketchydroide sketchydroide requested a review from marcaaron as a code owner June 28, 2022 21:25
@sketchydroide sketchydroide self-assigned this Jun 28, 2022
@sketchydroide sketchydroide requested a review from a team as a code owner June 30, 2022 14:30
@melvin-bot melvin-bot bot requested review from Beamanator and removed request for a team June 30, 2022 14:31
@sketchydroide
Copy link
Contributor Author

@marcaaron if you have some time I would appreciate a first glance at this one as well, I think I got the gist of it, but still not sure I did it right.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start, but a lot of changes left to make. Please let me know if there are specific questions!

_.each(reportIDsToFetchActions, (reportID) => {
const offset = ReportActions.dangerouslyGetReportActionsMaxSequenceNumber(reportID, false);
readOldestAction(reportID, offset);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these changes need to be removed. I'll try to break down everything and explain why it's incorrect...

Actions need to be tied to user actions

  • We should not be calling readOldestAction() as a direct replacement for fetchActions()
  • readOldestAction() would be called when we... read the oldest action and no other time
  • Reading the oldest action means that we are scrolling up on the report and new chat messages are loading

So what "action" should we use for this existing code?

  • None. IIRC we decided to remove this.
  • Why does it exist?
  • It's an optimization (possibly not needed) used by the front end to preload a bunch of chat messages for various recent reports that we know are "missing actions" based on the locally stored actions we already have.
  • Why are we using a timer? If we keep the code should it be removed?
  • We run this code at a significant delay to avoid performance issues from loading too many actions at once. If we are not removing the code right now we should keep the timer.

What should we do for now?

I think just remove the code entirely is fine. If we want to "preload" actions we can think of a better design and have it hook into the big "App Start" event. Chatted about this for a bit with @luacmartins in this doc section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this, feel like once we finish the refactor, we can figure if this is still needed and if that is the case come up with a better way of doing this.

Is this one of the reasons we sometimes have a white loading page?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Let's remove it and create a follow up issue to investigate.

I'm not sure about the white loading page and haven't experienced that myself - could be related or not.

*/
function readOldestAction(reportID, offset = 0) {
const hasFinishedLoadingData = {
onyxMethod: 'merge',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onyxMethod: 'merge',
onyxMethod: CONST.ONYX.METHOD.MERGE,

* @param {Number} offset
*/
function readOldestAction(reportID, offset = 0) {
const hasFinishedLoadingData = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name sounds a bit like a boolean (has, should, is, etc) and we should update to defaultReportActionsLoadingState or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason at some point this became a boolean in my head, really don't know why, when is so obviously not.

},
};

Onyx.set(ONYXKEYS.IS_LOADING_REPORT_ACTIONS, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Onyx.set(ONYXKEYS.IS_LOADING_REPORT_ACTIONS, true);

offset,
},
{
optimisticData: [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
optimisticData: [],
optimisticData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.IS_LOADING_REPORT_ACTIONS}${reportID}`,
value: {isLoading: true},
}],

function readOldestAction(reportID, offset = 0) {
const hasFinishedLoadingData = {
onyxMethod: 'merge',
key: `${ONYXKEYS.IS_LOADING_REPORT_ACTIONS}${reportID}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks kind of right, but there's a more correct way to do this.

If we want to start tracking the loading status for each report in this way we would want to create a "collection" key. Explained in the features of Onyx here, but let me know if you have any questions about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I would need to create a key in collections, and use that one instead? is that correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep you got it.

@@ -300,7 +300,7 @@ class ReportActionsView extends React.Component {
// Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments, unless we're near the beginning, in which
// case just get everything starting from 0.
const offset = Math.max(minSequenceNumber - CONST.REPORT.ACTIONS.LIMIT, 0);
Report.fetchActionsWithLoadingState(this.props.reportID, offset);
Report.readOldestAction(this.props.reportID, offset);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct because we are passing the "oldest action" to the API.

@@ -275,7 +275,7 @@ class ReportActionsView extends React.Component {
}

fetchData() {
Report.fetchActions(this.props.reportID);
Report.readOldestAction(this.props.reportID);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few more changes that need to happen in this file, but this one is incorrect as we are not reading the oldest action here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got it now, we need to call ReadOldestAction in the places we are calling fetchActions and passing an oldestAction

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost... we need to call ReadOldestAction when the user "reads the oldest action". Which is just another way of saying "pagination".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that makes sense.
I think we only have one place at the moment though

@@ -300,7 +300,7 @@ class ReportActionsView extends React.Component {
// Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments, unless we're near the beginning, in which
// case just get everything starting from 0.
const offset = Math.max(minSequenceNumber - CONST.REPORT.ACTIONS.LIMIT, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it would help to rename this variable oldestActionSequenceNumber


Onyx.set(ONYXKEYS.IS_LOADING_REPORT_ACTIONS, true);

API.write('ReadOldestAction',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set this as Write, but I think it should be read right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh hmm yes since we are not writing anything in the server I think that's correct.

src/libs/actions/Report.js Show resolved Hide resolved
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
},

@marcaaron marcaaron changed the title [WIP] Refactor Get_ReportHistory Refactor Get_ReportHistory Jul 20, 2022
remove successData for actions
@marcaaron
Copy link
Contributor

Changes look good, but we are still missing...

  • Test steps
  • package-lock.json changes that got merged in need to be removed as they should not be committed

Also we'll need a new Web-E PR with these changes -> https://github.com/Expensify/Web-Expensify/compare/marcaaron-stdClassSolution?expand=1 to fix the weird issue described in this Slack thread

@marcaaron marcaaron requested review from marcaaron and removed request for Beamanator July 21, 2022 02:06
@melvin-bot
Copy link

melvin-bot bot commented Jul 21, 2022

npm has a package.json file and a package-lock.json file. It seems you updated one without the other, which is usually a sign of a mistake. If you are updating a package make sure that you update the version in package.json then run npm install

1 similar comment
@melvin-bot
Copy link

melvin-bot bot commented Jul 21, 2022

npm has a package.json file and a package-lock.json file. It seems you updated one without the other, which is usually a sign of a mistake. If you are updating a package make sure that you update the version in package.json then run npm install

@sketchydroide
Copy link
Contributor Author

this needs to hold until https://github.com/Expensify/Web-Expensify/pull/34374 is CP/merged, but it's ready otherwise

@melvin-bot
Copy link

melvin-bot bot commented Jul 21, 2022

npm has a package.json file and a package-lock.json file. It seems you updated one without the other, which is usually a sign of a mistake. If you are updating a package make sure that you update the version in package.json then run npm install

Copy link
Contributor

@Beamanator Beamanator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looking real good! Just leaving a few tiny recommended changes

"integrity": "sha1-BqZgTWpV1L9BqaR9mHLXp42jHnM="
"integrity": "sha512-xpkr6sCDIYTPqzvjG8M3ncw1YOTaloWZOyrUmicoEifBEKzQzt+ooUpRpQ/AbOoJfO/p2ZKiyp79qHThzJDulQ=="
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, are we ok with committing these types of changes?

Mainly asking b/c it was recommended I don't commit these changes here: #9635 (comment)

And I think it makes sense to all agree or disagree what the best plan is 😅

cc @Julesssss

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @sketchydroide is on an M1 Mac, I think it's okay right? But yeah we should clarify this rule as the changes do look similar to yours @Beamanator.

I think the idea is that anyone on an M1 Mac should not see a diff when running npm i

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was commited by mistake, and @marcaaron already asked for the commit of this file to be reverted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reset this

@@ -1160,6 +1095,37 @@ function openReport(reportID) {
});
}

/**
* Gets the actions from the oldest unread action.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we clarify: Does this get actions from the oldest unread action and older? or and newer?

Not sure if my proposed addition is 100% clear even, but just from reading this I think ReadOldestAction is singular so we're only getting 1 action. If that's true, great - let's update this comment so it is clear we're only getting 1 action. Otherwise can we think about making the command ReadOldestActions and clarifying if we're getting oldest unread action & newer or & older?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this reads in to the older actions/messages, basically when you scroll up, so older and older

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about the plural part though. @marcaaron do you have thoughts on that?
We would need to change Web as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We (the user) are reading the "oldest action". This is synonymous with scrolling up to the oldest action stored locally - a.k.a. triggering a call to paginate the actions and load the next set. It should not be plural and we are not "only getting 1 action" we are getting the next set of historical actions.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@marcaaron marcaaron dismissed Beamanator’s stale review July 21, 2022 16:53

Both comments left have been resolved.

@marcaaron marcaaron merged commit d48e147 into main Jul 21, 2022
@marcaaron marcaaron deleted the afonseca_rrgh branch July 21, 2022 16:54
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by @marcaaron in version: 1.1.86-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

OSBotify commented Aug 1, 2022

🚀 Deployed to production by @yuwenmemon in version: 1.1.86-5 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants