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

close modal when opening new page using keyboard shortcut #15413

Merged

Conversation

situchan
Copy link
Contributor

Details

Fixed Issues

$ #14889
PROPOSAL: #14889 (comment)

Tests

Same as QA step

  • Verify that no errors appear in the JS console

Offline tests

Same as QA step

QA Steps

  1. Login with any account
  2. Navigate to Settings ->Payments
  3. Click on "Add payment method"
  4. Press (CTRL+K) on the keyboard
  5. Verify that payment method modal closes while opening Search page
  • Verify that no errors appear in the JS console

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 the expected offline behavior in the Offline steps 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 tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • 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 is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review 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
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • 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 */
    • 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 any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • 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.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
web.mov
Mobile Web - Chrome mchrome
Mobile Web - Safari

msafari

Desktop
desktop.mov
iOS

ios

Android android

NOTE: The main videos are in web, desktop. Other platforms are just app-working screenshot since keyboard shortcut is not available.

@situchan situchan requested a review from a team as a code owner February 23, 2023 15:58
@melvin-bot melvin-bot bot requested review from aimane-chnaif and puneetlath and removed request for a team February 23, 2023 15:58
@MelvinBot
Copy link

@puneetlath @aimane-chnaif One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

Comment on lines 33 to 36
componentDidMount() {
if (!this.props.isVisible) { return; }
Modal.setModalClose(this.props.onClose);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary? I think modals are mounted when before they are visible, but maybe this is just to make sure? If so please add a comment explaining why.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a special case for PopoverReportActionContextMenu.
I confirmed that isVisible is already true when component mount, so componentDidUpdate is never called.
image

}

componentWillUnmount() {
// we don't want to call the onModalHide on unmount
this.hideModal(this.props.isVisible);

// we don't want to call the onClose on unmount
Modal.setModalClose(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs an explanation of why

Copy link
Contributor Author

@situchan situchan Feb 23, 2023

Choose a reason for hiding this comment

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

we don't want to call the onClose on unmount
Is this explanation not enough?
I think this description is actually wrong.
This should be better description:
to handle case of modal unmounted with visible state

src/components/Popover/index.js Show resolved Hide resolved
src/libs/actions/Modal.js Outdated Show resolved Hide resolved
@situchan
Copy link
Contributor Author

done feedback

@aimane-chnaif
Copy link
Contributor

Latest changes look good to me. @pecanoro do you have any more concern before I fill checklist?

@pecanoro
Copy link
Contributor

@aimane-chnaif All good, unless I forgot something!

@aimane-chnaif
Copy link
Contributor

I tested with all modals across the app.
Since the solution is applied to all modals, I have some minor concerns for some modal types (though I don't think these are really issues).

  1. keyboard shortcut for keyboard shortcut modal switches visibility
  • Press Ctrl+I and it opens shortcut modal
  • Press Ctrl+I again and it closes currently open shortcut modal
keyboard.shortcut.mov

In production, when press again, it keeps shortcut modal open

  1. Right docked modals look same as navigation modals. Is it fine to close these kinds of modals too?
right_docked.mov
  1. Centered full screen modals:
centered.mov

cc: @puneetlath @pecanoro

@pecanoro
Copy link
Contributor

I would have to test all cases, but after seeing your videos, it seems to work great. I think it does not matter if pressing again the shortcut closes the modal, I actually like it more that way.

@aimane-chnaif
Copy link
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • 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 included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • 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 is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review 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 other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • 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 */
    • 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 any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • 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.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
web.mov
Mobile Web - Chrome
Mobile Web - Safari
Desktop
desktop.mov
iOS
Android

aimane-chnaif
aimane-chnaif previously approved these changes Feb 24, 2023
Copy link
Contributor

@aimane-chnaif aimane-chnaif left a comment

Choose a reason for hiding this comment

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

Actually I think those are not issues at all, same thought as @pecanoro so approving
@puneetlath all yours!

@puneetlath
Copy link
Contributor

Thanks for the thorough testing @aimane-chnaif. I agree that I see no problems with that behavior.

Copy link
Contributor

@puneetlath puneetlath left a comment

Choose a reason for hiding this comment

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

Mostly looks good. I think the comments could be clearer.

src/components/Modal/BaseModal.js Outdated Show resolved Hide resolved
src/components/Modal/BaseModal.js Outdated Show resolved Hide resolved
Co-authored-by: Puneet Lath <puneet@expensify.com>
Copy link
Contributor

@puneetlath puneetlath left a comment

Choose a reason for hiding this comment

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

Seems good to me. @pecanoro do you want to review again?

@pecanoro
Copy link
Contributor

Retested! I think it works great! Unless a missed some edge case!

@pecanoro pecanoro merged commit 9421a0e into Expensify:main Feb 24, 2023
@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.

@github-actions
Copy link
Contributor

Performance Comparison Report 📊

Significant Changes To Duration

Name Duration
Open Search Page TTI 588.447 ms → 691.561 ms (+103.114 ms, +17.5%) 🔴
Show details
Name Duration
Open Search Page TTI Baseline
Mean: 588.447 ms
Stdev: 20.511 ms (3.5%)
Runs: 550.0192869994789 550.8996989997104 565.3791910000145 566.1256099995226 566.3650719998404 570.9792479993775 571.09452399984 572.6295579997823 572.7047530002892 573.1555989999324 573.8054209994152 574.5997720006853 578.6958820000291 582.2061369996518 584.9282639995217 585.5901290001348 587.8080239994451 590.8888349998742 591.0880950000137 596.9378669997677 597.7823080001399 598.0344239994884 600.8124599996954 601.6879480006173 603.7399100000039 605.7966310000047 610.4595539998263 616.4286699993536 617.6162930000573 619.3340659998357 619.8330899998546 632.8790690004826

Current
Mean: 691.561 ms
Stdev: 26.504 ms (3.8%)
Runs: 632.1663410002366 640.6835540002212 644.7062579998747 654.7703050002456 658.3346349997446 658.7939050002024 677.838786999695 678.7542319996282 680.2816980006173 683.0190439997241 686.6383059993386 687.2071939995512 687.5295820003375 688.9455160005018 690.1096189999953 693.3208009991795 697.6958010001108 698.1894530002028 700.9278159998357 703.8067220002413 705.7233890006319 706.3346760002896 707.1793209994212 712.4437659997493 713.4064539996907 714.6105960002169 716.7368580000475 721.5767419999465 722.6982019999996 725.7165119992569 748.2414549998939

Meaningless Changes To Duration

Show entries
Name Duration
App start TTI 753.740 ms → 777.997 ms (+24.257 ms, +3.2%)
App start runJsBundle 211.406 ms → 213.500 ms (+2.094 ms, +1.0%)
App start nativeLaunch 19.600 ms → 20.233 ms (+0.633 ms, +3.2%)
App start regularAppStart 0.015 ms → 0.016 ms (+0.001 ms, +6.1%)
Show details
Name Duration
App start TTI Baseline
Mean: 753.740 ms
Stdev: 33.417 ms (4.4%)
Runs: 690.6300959996879 699.3949039997533 705.2243550000712 708.3730060001835 722.5473189996555 723.6313640000299 728.8910809997469 733.7366679999977 734.3346530003473 736.0724130002782 736.6151980003342 737.9139830004424 741.4230779996142 744.6546099996194 745.3112509995699 747.2153390003368 755.1775289997458 755.7874400001019 758.0003469996154 762.6818519998342 770.8894649995491 772.3901000004262 776.7703360002488 783.1120530003682 783.2749159997329 784.0564139997587 786.5126660000533 791.1906599998474 800.2887939997017 818.4256130000576 831.4148049997166

Current
Mean: 777.997 ms
Stdev: 23.807 ms (3.1%)
Runs: 730.7256599999964 740.4091280000284 742.0737079996616 751.333593999967 760.2410289999098 760.5312350001186 762.2576639996842 763.8235649997368 765.0069450000301 769.0221459995955 770.6794149996713 770.9318930003792 772.3197389999405 773.464833999984 775.293566999957 777.7433320004493 778.0133940000087 778.1884519997984 780.2834869995713 781.5776930004358 781.8853529999033 782.735027000308 790.1622259998694 796.3770979996771 797.9347580000758 804.8347199996933 808.9852179996669 810.1679490003735 815.591660999693 847.3126819999889
App start runJsBundle Baseline
Mean: 211.406 ms
Stdev: 21.104 ms (10.0%)
Runs: 178 187 188 188 190 193 194 196 197 199 199 201 202 204 206 207 209 210 211 211 211 211 218 220 225 228 231 232 244 246 264 265

Current
Mean: 213.500 ms
Stdev: 17.489 ms (8.2%)
Runs: 186 192 197 198 199 200 200 202 202 202 204 204 204 205 206 207 213 215 216 220 220 221 222 222 225 230 233 242 259 259
App start nativeLaunch Baseline
Mean: 19.600 ms
Stdev: 1.332 ms (6.8%)
Runs: 17 18 18 18 18 18 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 21 21 21 22 22 23

Current
Mean: 20.233 ms
Stdev: 1.383 ms (6.8%)
Runs: 18 18 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 22 22 22 22 23 24
App start regularAppStart Baseline
Mean: 0.015 ms
Stdev: 0.001 ms (7.5%)
Runs: 0.01269600074738264 0.0133050000295043 0.013508999720215797 0.013712000101804733 0.014119000174105167 0.014282000251114368 0.014322999864816666 0.014322999864816666 0.01432300079613924 0.014364000409841537 0.014566999860107899 0.014649000018835068 0.014689000323414803 0.01476999931037426 0.014810999855399132 0.014933000318706036 0.015054999850690365 0.015135999768972397 0.015258999541401863 0.015381000004708767 0.015502000227570534 0.01550300046801567 0.015544000081717968 0.015625 0.015786999836564064 0.016316999681293964 0.016642999835312366 0.016764000058174133 0.017171000130474567 0.0177819998934865

Current
Mean: 0.016 ms
Stdev: 0.001 ms (4.0%)
Runs: 0.0148930000141263 0.015054999850690365 0.015176999382674694 0.015217999927699566 0.015219000168144703 0.01534000039100647 0.015421000309288502 0.015461999922990799 0.015462000854313374 0.015583999454975128 0.015584000386297703 0.015665999613702297 0.015705999918282032 0.01570700015872717 0.015787999145686626 0.0157880000770092 0.01582799945026636 0.016032000072300434 0.01607200037688017 0.016112999990582466 0.016193999908864498 0.0163569999858737 0.01660200022161007 0.016642000526189804 0.0166830001398921 0.0166830001398921 0.016764000058174133 0.016885999590158463 0.017129999585449696 0.01729300059378147

@github-actions github-actions bot added the DeployBlockerCash This issue or pull request should block deployment label Feb 24, 2023
@github-actions
Copy link
Contributor

@Expensify/mobile-deployers 📣 Please look into this performance regression as it's a deploy blocker.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/pecanoro in version: 1.2.77-0 🚀

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

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/pecanoro in version: 1.2.77-0 🚀

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

@OSBotify
Copy link
Contributor

OSBotify commented Mar 2, 2023

🚀 Deployed to production by https://github.com/yuwenmemon in version: 1.2.77-4 🚀

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

componentDidUpdate(prevProps) {
if (prevProps.isVisible === this.props.isVisible) {
return;
}

Modal.willAlertModalBecomeVisible(this.props.isVisible);
Modal.setCloseModal(this.props.isVisible ? this.props.onClose : null);
Copy link
Contributor

Choose a reason for hiding this comment

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

@situchan Do you recall why this call is done in that way? i.e. why we need to set the callback to null if this modal is not visible? Why we don't do it the same as componentDidMount:

if (this.props.isVisible) {
    Modal.setModalClose(this.props.onClose);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We pass onClose callback to setCloseModal to allow other parts of the app to close this modal manually.
If already closed, why we still need to have this stale callback? Also see below code why it's needed:

function close(onModalCloseCallback: () => void, isNavigating = true) {
if (!closeModal) {
// If modal is already closed, no need to wait for modal close. So immediately call callback.
if (onModalCloseCallback) {
onModalCloseCallback();
}
onModalClose = null;
return;
}

Btw, does that cause any issue?

Copy link
Contributor

Choose a reason for hiding this comment

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

If already closed, why we still need to have this stale callback?

We may have other modals that did set the callback. If we render a new modal invisible, the old visible modal close callback will be overwritten.

Copy link
Contributor

Choose a reason for hiding this comment

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

Btw, does that cause any issue?

Not exactly, but the functional rewrite of the modal seems to be the cause of the regression. I think there was a misunderstanding of how the code is supposed to be executed. Here at this point, componentDidMount and componentDidUpdate call Modal.setModalClose differently (as explained above) but after the functional migration we only use useEffect and both logic get merged to look like componentDidUpdate,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DeployBlockerCash This issue or pull request should block deployment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants