From d69426ce0e0f38ffe19837746826a388ce9046bb Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 11 Aug 2023 15:25:57 -0600 Subject: [PATCH 001/386] Fix path to build Android correctly --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 60d60934c2ba..ec68c74472de 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -17,7 +17,7 @@ platform :android do desc "Generate a new local APK for e2e testing" lane :build_e2e do ENV["ENVFILE"]="tests/e2e/.env.e2e" - ENV["ENTRY_FILE"]="#{Dir.pwd}/../src/libs/E2E/reactNativeLaunchingTest.js" + ENV["ENTRY_FILE"]="../src/libs/E2E/reactNativeLaunchingTest.js" ENV["E2E_TESTING"]="true" gradle( From 85f0d93e444f6437bff64eba4809c66ea7f2ea68 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 11 Aug 2023 15:31:03 -0600 Subject: [PATCH 002/386] Add `IS_MERGED` output --- .github/actions/javascript/getPullRequestDetails/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/javascript/getPullRequestDetails/action.yml b/.github/actions/javascript/getPullRequestDetails/action.yml index a59cf55bdf9f..6704d5220851 100644 --- a/.github/actions/javascript/getPullRequestDetails/action.yml +++ b/.github/actions/javascript/getPullRequestDetails/action.yml @@ -15,6 +15,8 @@ outputs: description: 'The merge_commit_sha of the given pull request' MERGE_ACTOR: description: 'The actor who merged the pull request' + IS_MERGED: + description: 'True if the pull request is merged' runs: using: 'node16' main: './index.js' From 45dd2b4e1b00bcea3ce29aa659874ea24f40c795 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 11 Aug 2023 15:35:14 -0600 Subject: [PATCH 003/386] Add two more missing outputs --- .github/actions/javascript/getPullRequestDetails/action.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/actions/javascript/getPullRequestDetails/action.yml b/.github/actions/javascript/getPullRequestDetails/action.yml index 6704d5220851..ed2c60f018a1 100644 --- a/.github/actions/javascript/getPullRequestDetails/action.yml +++ b/.github/actions/javascript/getPullRequestDetails/action.yml @@ -13,10 +13,14 @@ inputs: outputs: MERGE_COMMIT_SHA: description: 'The merge_commit_sha of the given pull request' + HEAD_COMMIT_SHA: + description: 'The head_commit_sha of the given pull request' MERGE_ACTOR: description: 'The actor who merged the pull request' IS_MERGED: description: 'True if the pull request is merged' + FORKED_REPO_URL: + description: 'Output forked repo URL if PR includes changes from a fork' runs: using: 'node16' main: './index.js' From 20c1e05b2a54b3b9d06124839ecb6c6833e3a7f5 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 11 Aug 2023 15:40:54 -0600 Subject: [PATCH 004/386] Temp comment out fork checkout --- .github/workflows/e2ePerformanceTests.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index fe364b376e3b..548f7e901667 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -81,12 +81,13 @@ jobs: - name: Unmerged PR - Fetch head ref of unmerged PR if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} run: | - if [[ ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} != '' ]]; then - git remote add pr_remote ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} - git fetch pr_remote ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 - else +# TODO: UNDO THIS +# if [[ ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} != '' ]]; then +# git remote add pr_remote ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} +# git fetch pr_remote ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 +# else git fetch origin ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 - fi +# fi - name: Unmerged PR - Set dummy git credentials before merging if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} From 0cd275462293f0ca4238545fbb8037cc40904e8d Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 11 Aug 2023 15:41:48 -0600 Subject: [PATCH 005/386] Another temporary fix --- .github/workflows/e2ePerformanceTests.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 548f7e901667..7888c8c47c25 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -81,13 +81,7 @@ jobs: - name: Unmerged PR - Fetch head ref of unmerged PR if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} run: | -# TODO: UNDO THIS -# if [[ ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} != '' ]]; then -# git remote add pr_remote ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} -# git fetch pr_remote ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 -# else git fetch origin ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 -# fi - name: Unmerged PR - Set dummy git credentials before merging if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} From f7f3a45a86728c1cccd49bde0407d05a1f7f1c31 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Tue, 15 Aug 2023 15:45:25 -0600 Subject: [PATCH 006/386] Tweak path --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ec68c74472de..5dfccb4d93ec 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -17,7 +17,7 @@ platform :android do desc "Generate a new local APK for e2e testing" lane :build_e2e do ENV["ENVFILE"]="tests/e2e/.env.e2e" - ENV["ENTRY_FILE"]="../src/libs/E2E/reactNativeLaunchingTest.js" + ENV["ENTRY_FILE"]="{Dir.pwd}/src/libs/E2E/reactNativeLaunchingTest.js" ENV["E2E_TESTING"]="true" gradle( From bd51745eb262a09b416b76c2cbc7f451c6edcedd Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Tue, 15 Aug 2023 16:23:32 -0600 Subject: [PATCH 007/386] Try fixing path yet again! --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5dfccb4d93ec..cd11c5cf9e98 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -17,7 +17,7 @@ platform :android do desc "Generate a new local APK for e2e testing" lane :build_e2e do ENV["ENVFILE"]="tests/e2e/.env.e2e" - ENV["ENTRY_FILE"]="{Dir.pwd}/src/libs/E2E/reactNativeLaunchingTest.js" + ENV["ENTRY_FILE"]="src/libs/E2E/reactNativeLaunchingTest.js" ENV["E2E_TESTING"]="true" gradle( From 64e4effbd2c266308c09181007cd4d416c84f6e5 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Tue, 15 Aug 2023 16:52:54 -0600 Subject: [PATCH 008/386] Undo test commit --- .github/workflows/e2ePerformanceTests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 7888c8c47c25..fe364b376e3b 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -81,7 +81,12 @@ jobs: - name: Unmerged PR - Fetch head ref of unmerged PR if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} run: | + if [[ ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} != '' ]]; then + git remote add pr_remote ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} + git fetch pr_remote ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 + else git fetch origin ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 + fi - name: Unmerged PR - Set dummy git credentials before merging if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} From 9debab5bceb6bb778ac7ed83589159096c05dc32 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Tue, 15 Aug 2023 17:14:28 -0600 Subject: [PATCH 009/386] Testing full flow --- .github/workflows/e2ePerformanceTests.yml | 60 +++-------------------- 1 file changed, 6 insertions(+), 54 deletions(-) diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index fe364b376e3b..557af1a04786 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -16,42 +16,6 @@ on: required: true jobs: - buildBaseline: - runs-on: ubuntu-latest-xl - name: Build apk from latest release as a baseline - outputs: - VERSION: ${{ steps.getMostRecentRelease.outputs.VERSION }} - steps: - - uses: actions/checkout@v3 - - - name: Get most recent release version - id: getMostRecentRelease - run: echo "VERSION=$(gh release list --limit 1 | awk '{ print $1 }')" >> "$GITHUB_OUTPUT" - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Check if there's an existing artifact for this baseline - id: checkForExistingArtifact - uses: xSAVIKx/artifact-exists-action@3c5206b1411c0d2fc0840f56b7140646933d9d6a - with: - name: baseline-apk-${{ steps.getMostRecentRelease.outputs.VERSION }} - - - name: Skip build if there's already an existing artifact for the baseline - if: ${{ fromJSON(steps.checkForExistingArtifact.outputs.exists) }} - run: echo 'APK for baseline ${{ steps.getMostRecentRelease.outputs.VERSION }} already exists, reusing existing build' - - - name: Checkout "Baseline" commit (last release) - if: ${{ !fromJSON(steps.checkForExistingArtifact.outputs.exists) }} - run: | - git fetch origin tag ${{ steps.getMostRecentRelease.outputs.VERSION }} --no-tags --depth=1 - git switch --detach ${{ steps.getMostRecentRelease.outputs.VERSION }} - - - name: Build APK - if: ${{ !fromJSON(steps.checkForExistingArtifact.outputs.exists) }} - uses: Expensify/App/.github/actions/composite/buildAndroidAPK@main - with: - ARTIFACT_NAME: baseline-apk-${{ steps.getMostRecentRelease.outputs.VERSION }} - buildDelta: runs-on: ubuntu-latest-xl name: Build apk from delta ref @@ -81,12 +45,7 @@ jobs: - name: Unmerged PR - Fetch head ref of unmerged PR if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} run: | - if [[ ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} != '' ]]; then - git remote add pr_remote ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} - git fetch pr_remote ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 - else git fetch origin ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 - fi - name: Unmerged PR - Set dummy git credentials before merging if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} @@ -119,7 +78,7 @@ jobs: runTestsInAWS: runs-on: ubuntu-latest - needs: [buildBaseline, buildDelta] + needs: [buildDelta] name: Run E2E tests in AWS device farm steps: - uses: actions/checkout@v3 @@ -127,25 +86,18 @@ jobs: - name: Make zip directory for everything to send to AWS Device Farm run: mkdir zip - - name: Download baseline APK - uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b - id: downloadBaselineAPK - with: - name: baseline-apk-${{ needs.buildBaseline.outputs.VERSION }} - path: zip - - # The downloaded artifact will be a file named "app-e2eRelease.apk" so we have to rename it - - name: Rename baseline APK - run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-baseline.apk" - - name: Download delta APK uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b + id: downloadDeltaAPK with: name: delta-apk-${{ needs.buildDelta.outputs.DELTA_REF }} path: zip - name: Rename delta APK - run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-compare.apk" + run: mv "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2eRelease-compare.apk" + + - name: Copy Delta APK as compare for testing + run: mv "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2eRelease-compare.apk" "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2eRelease-baseline.apk" - name: Copy e2e code into zip folder run: cp -r tests/e2e zip From e1b500b38a023b5c82b99f0adcd0acac5b73b9a2 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Wed, 16 Aug 2023 11:12:06 -0600 Subject: [PATCH 010/386] Add more logs --- src/libs/E2E/tests/openSearchPageTest.e2e.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libs/E2E/tests/openSearchPageTest.e2e.js b/src/libs/E2E/tests/openSearchPageTest.e2e.js index 2f0f72f35bdd..12fcfc3fd1a7 100644 --- a/src/libs/E2E/tests/openSearchPageTest.e2e.js +++ b/src/libs/E2E/tests/openSearchPageTest.e2e.js @@ -7,12 +7,16 @@ import CONST from '../../../CONST'; const test = () => { // check for login (if already logged in the action will simply resolve) + console.debug('[E2E] Logging in for search'); + E2ELogin().then((neededLogin) => { if (neededLogin) { // we don't want to submit the first login to the results return E2EClient.submitTestDone(); } + console.debug('[E2E] Logged in, getting search metrics and submitting them…'); + Performance.subscribeToMeasurements((entry) => { if (entry.name !== CONST.TIMING.SEARCH_RENDER) { return; @@ -21,7 +25,14 @@ const test = () => { E2EClient.submitTestResults({ name: 'Open Search Page TTI', duration: entry.duration, - }).then(E2EClient.submitTestDone); + }) + .then(() => { + console.debug('[E2E] Done with search, exiting…'); + E2EClient.submitTestDone(); + }) + .catch((err) => { + console.debug('[E2E] Error while submitting test results:', err); + }); }); Navigation.navigate(ROUTES.SEARCH); From 6a93aeb4167656de56731b75e20e4acbc114113b Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Wed, 16 Aug 2023 13:10:24 -0600 Subject: [PATCH 011/386] Add more logs --- src/libs/E2E/tests/openSearchPageTest.e2e.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/E2E/tests/openSearchPageTest.e2e.js b/src/libs/E2E/tests/openSearchPageTest.e2e.js index 12fcfc3fd1a7..ded45f55fd9c 100644 --- a/src/libs/E2E/tests/openSearchPageTest.e2e.js +++ b/src/libs/E2E/tests/openSearchPageTest.e2e.js @@ -18,10 +18,12 @@ const test = () => { console.debug('[E2E] Logged in, getting search metrics and submitting them…'); Performance.subscribeToMeasurements((entry) => { + console.debug(`[E2E] Entry: ${entry}`); if (entry.name !== CONST.TIMING.SEARCH_RENDER) { return; } + console.debug(`[E2E] Submitting!`); E2EClient.submitTestResults({ name: 'Open Search Page TTI', duration: entry.duration, From 25af1459173556bef2f09ba0c8dc87db9e2fb707 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Wed, 16 Aug 2023 14:13:20 -0600 Subject: [PATCH 012/386] =?UTF-8?q?Fix=20logs=20=F0=9F=99=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libs/E2E/tests/openSearchPageTest.e2e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/E2E/tests/openSearchPageTest.e2e.js b/src/libs/E2E/tests/openSearchPageTest.e2e.js index ded45f55fd9c..e96d70c95d94 100644 --- a/src/libs/E2E/tests/openSearchPageTest.e2e.js +++ b/src/libs/E2E/tests/openSearchPageTest.e2e.js @@ -18,7 +18,7 @@ const test = () => { console.debug('[E2E] Logged in, getting search metrics and submitting them…'); Performance.subscribeToMeasurements((entry) => { - console.debug(`[E2E] Entry: ${entry}`); + console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); if (entry.name !== CONST.TIMING.SEARCH_RENDER) { return; } From 028b384dce0f9888e0c20f4fd7a5cc1f6bd5586f Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Wed, 16 Aug 2023 15:48:21 -0600 Subject: [PATCH 013/386] Only navigate to search bar once sidebar is loaded --- src/libs/E2E/tests/openSearchPageTest.e2e.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/E2E/tests/openSearchPageTest.e2e.js b/src/libs/E2E/tests/openSearchPageTest.e2e.js index e96d70c95d94..3b2d91322cf0 100644 --- a/src/libs/E2E/tests/openSearchPageTest.e2e.js +++ b/src/libs/E2E/tests/openSearchPageTest.e2e.js @@ -18,6 +18,12 @@ const test = () => { console.debug('[E2E] Logged in, getting search metrics and submitting them…'); Performance.subscribeToMeasurements((entry) => { + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + console.debug(`[E2E] Sidebar loaded, navigating to search route…`); + Navigation.navigate(ROUTES.SEARCH); + return; + } + console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); if (entry.name !== CONST.TIMING.SEARCH_RENDER) { return; @@ -36,8 +42,6 @@ const test = () => { console.debug('[E2E] Error while submitting test results:', err); }); }); - - Navigation.navigate(ROUTES.SEARCH); }); }; From 82713e6d8cf169d33e7a616b1bede527a94a7cf7 Mon Sep 17 00:00:00 2001 From: Jakub Trzebiatowski Date: Mon, 21 Aug 2023 10:29:40 +0200 Subject: [PATCH 014/386] Remove unused attachment placeholder code --- src/pages/home/report/ReportActionItem.js | 1 + .../home/report/ReportActionItemFragment.js | 19 ------------------- .../home/report/ReportActionItemMessage.js | 1 - .../home/report/ReportActionItemSingle.js | 1 - 4 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 8700327a168a..5dda82145f21 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -549,6 +549,7 @@ function ReportActionItem(props) { diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 009c1118400b..b437010efe75 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -1,5 +1,4 @@ import React, {memo} from 'react'; -import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import reportActionFragmentPropTypes from './reportActionFragmentPropTypes'; @@ -46,9 +45,6 @@ const propTypes = { source: PropTypes.string, }), - /** Does this fragment belong to a reportAction that has not yet loaded? */ - loading: PropTypes.bool, - /** The reportAction's source */ source: PropTypes.oneOf(['Chronos', 'email', 'ios', 'android', 'web', 'email', '']), @@ -78,7 +74,6 @@ const defaultProps = { type: '', source: '', }, - loading: false, isSingleLine: false, source: '', style: [], @@ -89,20 +84,6 @@ const defaultProps = { function ReportActionItemFragment(props) { switch (props.fragment.type) { case 'COMMENT': { - // If this is an attachment placeholder, return the placeholder component - if (props.isAttachment && props.loading) { - return Str.isImage(props.attachmentInfo.name) ? ( - `} /> - ) : ( - - - - ); - } const {html, text} = props.fragment; // Threaded messages display "[Deleted message]" instead of being hidden altogether. diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 7dcb2b51dbf3..fbad50c4f7e6 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -48,7 +48,6 @@ function ReportActionItemMessage(props) { pendingAction={props.action.pendingAction} source={lodashGet(props.action, 'originalMessage.source')} accountID={props.action.actorAccountID} - loading={props.action.isLoading} style={props.style} /> )) diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index c00f98c613e4..d1805cd148da 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -231,7 +231,6 @@ function ReportActionItemSingle(props) { accountID={actorAccountID} fragment={fragment} isAttachment={props.action.isAttachment} - isLoading={props.action.isLoading} delegateAccountID={props.action.delegateAccountID} isSingleLine actorIcon={icon} From 49f20da8dd368313b7c7feaa7300419ee66a5010 Mon Sep 17 00:00:00 2001 From: Jakub Trzebiatowski Date: Mon, 21 Aug 2023 11:08:52 +0200 Subject: [PATCH 015/386] Remove the remaining uses of the report action `isLoading` property --- src/pages/home/report/ReportActionItem.js | 3 +-- src/pages/home/report/reportActionPropTypes.js | 3 --- src/styles/StyleUtils.js | 6 +++--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 5dda82145f21..fe8eb4638e98 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -549,8 +549,7 @@ function ReportActionItem(props) { Date: Mon, 21 Aug 2023 09:01:26 -1000 Subject: [PATCH 016/386] Save queued updates to memory only --- src/ONYXKEYS.js | 3 --- src/libs/actions/QueuedOnyxUpdates.js | 16 +++++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 27e7f9f0ecf3..093ef83ef62b 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -24,9 +24,6 @@ export default { // Note: These are Persisted Requests - not all requests in the main queue as the key name might lead one to believe PERSISTED_REQUESTS: 'networkRequestQueue', - // Onyx updates from a response, or success or failure data from a request. - QUEUED_ONYX_UPDATES: 'queuedOnyxUpdates', - // Stores current date CURRENT_DATE: 'currentDate', diff --git a/src/libs/actions/QueuedOnyxUpdates.js b/src/libs/actions/QueuedOnyxUpdates.js index 486108dd56cf..f13ce83dcb97 100644 --- a/src/libs/actions/QueuedOnyxUpdates.js +++ b/src/libs/actions/QueuedOnyxUpdates.js @@ -1,24 +1,18 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../../ONYXKEYS'; - -// In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. +// In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates. let queuedOnyxUpdates = []; -Onyx.connect({ - key: ONYXKEYS.QUEUED_ONYX_UPDATES, - callback: (val) => (queuedOnyxUpdates = val || []), -}); /** * @param {Array} updates Onyx updates to queue for later - * @returns {Promise} + * @returns {Promise} */ function queueOnyxUpdates(updates) { - return Onyx.set(ONYXKEYS.QUEUED_ONYX_UPDATES, [...queuedOnyxUpdates, ...updates]); + queuedOnyxUpdates.concat(updates); + return Promise.resolve(); } function clear() { - Onyx.set(ONYXKEYS.QUEUED_ONYX_UPDATES, null); + queuedOnyxUpdates = []; } /** From 6307d475240e777bc6f3de1f167a6dc5ed802310 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 21 Aug 2023 09:05:30 -1000 Subject: [PATCH 017/386] concat returns a new array --- src/libs/actions/QueuedOnyxUpdates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/QueuedOnyxUpdates.js b/src/libs/actions/QueuedOnyxUpdates.js index f13ce83dcb97..d8f2d0b2519c 100644 --- a/src/libs/actions/QueuedOnyxUpdates.js +++ b/src/libs/actions/QueuedOnyxUpdates.js @@ -7,7 +7,7 @@ let queuedOnyxUpdates = []; * @returns {Promise} */ function queueOnyxUpdates(updates) { - queuedOnyxUpdates.concat(updates); + queuedOnyxUpdates = queuedOnyxUpdates.concat(updates); return Promise.resolve(); } From f2e47536add964b8f38a8a22f88609c012afc36f Mon Sep 17 00:00:00 2001 From: Jakub Trzebiatowski Date: Tue, 22 Aug 2023 10:00:30 +0200 Subject: [PATCH 018/386] ReportActionItemFragment: Remove unused isAttachment property --- src/pages/home/report/ReportActionItemFragment.js | 4 ---- src/pages/home/report/ReportActionItemMessage.js | 1 - src/pages/home/report/ReportActionItemSingle.js | 1 - 3 files changed, 6 deletions(-) diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index b437010efe75..2988d62f8d38 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -27,9 +27,6 @@ const propTypes = { /** The message fragment needing to be displayed */ fragment: reportActionFragmentPropTypes.isRequired, - /** Is this fragment an attachment? */ - isAttachment: PropTypes.bool, - /** If this fragment is attachment than has info? */ attachmentInfo: PropTypes.shape({ /** The file name of attachment */ @@ -67,7 +64,6 @@ const propTypes = { }; const defaultProps = { - isAttachment: false, attachmentInfo: { name: '', size: 0, diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index fbad50c4f7e6..2850b396e113 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -42,7 +42,6 @@ function ReportActionItemMessage(props) { Date: Tue, 22 Aug 2023 10:07:20 +0200 Subject: [PATCH 019/386] Run Prettier --- src/pages/home/report/ReportActionItem.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 6d2b5ae14270..36d437ccb82f 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -556,12 +556,7 @@ function ReportActionItem(props) { draftMessage={props.draftMessage} isChronosReport={ReportUtils.chatIncludesChronos(originalReport)} /> - + ReportActions.clearReportActionErrors(props.report.reportID, props.action)} pendingAction={props.draftMessage ? null : props.action.pendingAction} From e08c5b4acafe23c27cffb18afad2aa2c6d079f91 Mon Sep 17 00:00:00 2001 From: Jakub Trzebiatowski Date: Tue, 22 Aug 2023 11:53:28 +0200 Subject: [PATCH 020/386] getReportActionItemStyle: Remove effectively unused hasError argument --- src/pages/home/report/ReportActionItem.js | 2 +- src/styles/StyleUtils.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 36d437ccb82f..fdf588921995 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -556,7 +556,7 @@ function ReportActionItem(props) { draftMessage={props.draftMessage} isChronosReport={ReportUtils.chatIncludesChronos(originalReport)} /> - + ReportActions.clearReportActionErrors(props.report.reportID, props.action)} pendingAction={props.draftMessage ? null : props.action.pendingAction} diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 5aeb28d83e0e..6bb159a97297 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -637,10 +637,9 @@ function getLoginPagePromoStyle() { * Generate the styles for the ReportActionItem wrapper view. * * @param {Boolean} [isHovered] - * @param {Boolean} [hasError] * @returns {Object} */ -function getReportActionItemStyle(isHovered = false, hasError = false) { +function getReportActionItemStyle(isHovered = false) { return { display: 'flex', justifyContent: 'space-between', @@ -648,7 +647,7 @@ function getReportActionItemStyle(isHovered = false, hasError = false) { ? themeColors.hoverComponentBG : // Warning: Setting this to a non-transparent color will cause unread indicator to break on Android colors.transparent, - opacity: hasError ? 0.5 : 1, + opacity: 1, ...styles.cursorInitial, }; } From 3be5e6909a86a33a9c7c749c2378b14ad1ab243c Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Fri, 25 Aug 2023 16:36:54 +0200 Subject: [PATCH 021/386] Separate ReportScreenContext to prevent cyclic re-renders --- .../ReportActionItemEmojiReactions.js | 4 +- src/hooks/useReportScrollManager/index.js | 4 +- .../useReportScrollManager/index.native.js | 4 +- src/pages/home/ReportScreen.js | 188 +++++++++--------- src/pages/home/ReportScreenContext.js | 9 +- src/pages/home/report/ReportActionItem.js | 4 +- src/pages/home/report/ReportActionsView.js | 7 +- 7 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.js index ec2755f1a5dd..4f590b43e180 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.js +++ b/src/components/Reactions/ReportActionItemEmojiReactions.js @@ -13,7 +13,7 @@ import EmojiReactionsPropTypes from './EmojiReactionsPropTypes'; import Tooltip from '../Tooltip'; import ReactionTooltipContent from './ReactionTooltipContent'; import * as EmojiUtils from '../../libs/EmojiUtils'; -import ReportScreenContext from '../../pages/home/ReportScreenContext'; +import {ReactionListContext} from '../../pages/home/ReportScreenContext'; const propTypes = { emojiReactions: EmojiReactionsPropTypes, @@ -41,7 +41,7 @@ const defaultProps = { }; function ReportActionItemEmojiReactions(props) { - const {reactionListRef} = useContext(ReportScreenContext); + const reactionListRef = useContext(ReactionListContext); const popoverReactionListAnchor = useRef(null); let totalReactionCount = 0; diff --git a/src/hooks/useReportScrollManager/index.js b/src/hooks/useReportScrollManager/index.js index 0cf09146553c..9a3303504b92 100644 --- a/src/hooks/useReportScrollManager/index.js +++ b/src/hooks/useReportScrollManager/index.js @@ -1,8 +1,8 @@ import {useContext, useCallback} from 'react'; -import ReportScreenContext from '../../pages/home/ReportScreenContext'; +import {ActionListContext} from '../../pages/home/ReportScreenContext'; function useReportScrollManager() { - const {flatListRef} = useContext(ReportScreenContext); + const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because diff --git a/src/hooks/useReportScrollManager/index.native.js b/src/hooks/useReportScrollManager/index.native.js index 35af064cb062..d44a40222ca5 100644 --- a/src/hooks/useReportScrollManager/index.native.js +++ b/src/hooks/useReportScrollManager/index.native.js @@ -1,8 +1,8 @@ import {useContext, useCallback} from 'react'; -import ReportScreenContext from '../../pages/home/ReportScreenContext'; +import {ActionListContext} from '../../pages/home/ReportScreenContext'; function useReportScrollManager() { - const {flatListRef} = useContext(ReportScreenContext); + const flatListRef = useContext(ActionListContext); /** * Scroll to the provided index. diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 83f0e4a6d506..16284c2bd9c5 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -33,7 +33,7 @@ import getIsReportFullyVisible from '../../libs/getIsReportFullyVisible'; import MoneyRequestHeader from '../../components/MoneyRequestHeader'; import MoneyReportHeader from '../../components/MoneyReportHeader'; import * as ComposerActions from '../../libs/actions/Composer'; -import ReportScreenContext from './ReportScreenContext'; +import {ActionListContext, ReactionListContext} from './ReportScreenContext'; import TaskHeaderActionButton from '../../components/TaskHeaderActionButton'; import DragAndDropProvider from '../../components/DragAndDrop/Provider'; @@ -276,111 +276,107 @@ class ReportScreen extends React.Component { } return ( - - - + + - - {headerView} - {ReportUtils.isTaskReport(this.props.report) && this.props.isSmallScreenWidth && ReportUtils.isOpenTaskReport(this.props.report) && ( - - - - + + {headerView} + {ReportUtils.isTaskReport(this.props.report) && this.props.isSmallScreenWidth && ReportUtils.isOpenTaskReport(this.props.report) && ( + + + + + - - )} - - {Boolean(this.props.accountManagerReportID) && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && ( - - )} - - { - // Rounding this value for comparison because they can look like this: 411.9999694824219 - const skeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height); - - // Only set state when the height changes to avoid unnecessary renders - if (reportActionsListViewHeight === skeletonViewContainerHeight) return; - - // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it - // takes up so we can set the skeleton view container height. - if (skeletonViewContainerHeight === 0) { - return; - } - reportActionsListViewHeight = skeletonViewContainerHeight; - this.setState({skeletonViewContainerHeight}); - }} - > - {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && ( - )} - - {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then - we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} - {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && ( - - )} - - {this.isReportReadyForDisplay() && ( - <> - + {Boolean(this.props.accountManagerReportID) && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && ( + + )} + + { + // Rounding this value for comparison because they can look like this: 411.9999694824219 + const skeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height); + + // Only set state when the height changes to avoid unnecessary renders + if (reportActionsListViewHeight === skeletonViewContainerHeight) return; + + // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it + // takes up so we can set the skeleton view container height. + if (skeletonViewContainerHeight === 0) { + return; + } + reportActionsListViewHeight = skeletonViewContainerHeight; + this.setState({skeletonViewContainerHeight}); + }} + > + {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && ( + - - )} + )} - {!this.isReportReadyForDisplay() && ( - - )} - - - - - + {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then + we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} + {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && ( + + )} + + {this.isReportReadyForDisplay() && ( + <> + + + )} + + {!this.isReportReadyForDisplay() && ( + + )} + + + + + + ); } } diff --git a/src/pages/home/ReportScreenContext.js b/src/pages/home/ReportScreenContext.js index 2f79d6ae9432..0be1882699f4 100644 --- a/src/pages/home/ReportScreenContext.js +++ b/src/pages/home/ReportScreenContext.js @@ -1,4 +1,9 @@ import {createContext} from 'react'; -const ReportScreenContext = createContext(); -export default ReportScreenContext; +const ActionListContext = createContext(); +const ReactionListContext = createContext(); + +export { + ActionListContext, + ReactionListContext +} \ No newline at end of file diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index e5b199d1c994..4a0c0d7bd3ba 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -64,7 +64,7 @@ import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import * as store from '../../../libs/actions/ReimbursementAccount/store'; import * as BankAccounts from '../../../libs/actions/BankAccounts'; -import ReportScreenContext from '../ReportScreenContext'; +import { ReactionListContext } from '../ReportScreenContext'; import Permissions from '../../../libs/Permissions'; const propTypes = { @@ -127,7 +127,7 @@ function ReportActionItem(props) { const [isContextMenuActive, setIsContextMenuActive] = useState(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)); const [isHidden, setIsHidden] = useState(false); const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - const {reactionListRef} = useContext(ReportScreenContext); + const reactionListRef = useContext(ReactionListContext); const textInputRef = useRef(); const popoverAnchorRef = useRef(); const downloadedPreviews = useRef([]); diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index da475e61f749..70eb65ade3ea 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -19,7 +19,7 @@ import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import reportPropTypes from '../../reportPropTypes'; import PopoverReactionList from './ReactionList/PopoverReactionList'; import getIsReportFullyVisible from '../../../libs/getIsReportFullyVisible'; -import ReportScreenContext from '../ReportScreenContext'; +import { ReactionListContext } from '../ReportScreenContext'; const propTypes = { /** The report currently being looked at */ @@ -54,10 +54,9 @@ const defaultProps = { }; function ReportActionsView(props) { - const context = useContext(ReportScreenContext); useCopySelectionHelper(); - + const reactionListRef = useContext(ReactionListContext) const didLayout = useRef(false); const didSubscribeToReportTypingEvents = useRef(false); const hasCachedActions = useRef(_.size(props.reportActions) > 0); @@ -189,7 +188,7 @@ function ReportActionsView(props) { loadMoreChats={loadMoreChats} policy={props.policy} /> - + ); } From 6125c381cde29277f150f1a4156a6b96af05f150 Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Mon, 28 Aug 2023 13:44:09 +0200 Subject: [PATCH 022/386] Get rid of full list re-render when marking messages as unread --- src/pages/home/report/ReportActionsList.js | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 7f897ee825fb..1c0c88e20ec4 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -114,9 +114,9 @@ function ReportActionsList({ const readActionSkipped = useRef(false); const reportActionSize = useRef(sortedReportActions.length); - // Considering that renderItem is enclosed within a useCallback, marking it as "read" twice will retain the value as "true," preventing the useCallback from re-executing. - // However, if we create and listen to an object, it will lead to a new useCallback execution. - const [messageManuallyMarked, setMessageManuallyMarked] = useState({read: false}); + // This state is used to force a re-render when the user manually marks a message as unread + // by using a timestamp you can force re-renders without having to worry about if another message was marked as unread before + const [messageManuallyMarkedUnread, setMessageManuallyMarkedUnread] = useState(0); const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); const animatedStyles = useAnimatedStyle(() => ({ opacity: opacity.value, @@ -163,15 +163,14 @@ function ReportActionsList({ useEffect(() => { const didManuallyMarkReportAsUnread = report.lastReadTime < DateUtils.getDBTime() && ReportUtils.isUnread(report); - if (!didManuallyMarkReportAsUnread) { - setMessageManuallyMarked({read: false}); - return; + if (didManuallyMarkReportAsUnread) { + // Clearing the current unread marker so that it can be recalculated + currentUnreadMarker.current = null; + setMessageManuallyMarkedUnread(new Date().getTime()); + } else { + setMessageManuallyMarkedUnread(0); } - // Clearing the current unread marker so that it can be recalculated - currentUnreadMarker.current = null; - setMessageManuallyMarked({read: true}); - // We only care when a new lastReadTime is set in the report // eslint-disable-next-line react-hooks/exhaustive-deps }, [report.lastReadTime]); @@ -230,7 +229,7 @@ function ReportActionsList({ const isCurrentMessageUnread = isMessageUnread(reportAction, report.lastReadTime); shouldDisplayNewMarker = isCurrentMessageUnread && !isMessageUnread(nextMessage, report.lastReadTime); - if (!messageManuallyMarked.read) { + if (!messageManuallyMarkedUnread) { shouldDisplayNewMarker = shouldDisplayNewMarker && reportAction.actorAccountID !== Report.getCurrentUserAccountID(); } const canDisplayMarker = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < userActiveSince.current : true; @@ -272,7 +271,7 @@ function ReportActionsList({ /> ); }, - [report, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarked], + [report, hasOutstandingIOU, sortedReportActions, mostRecentIOUReportActionID, messageManuallyMarkedUnread], ); // Native mobile does not render updates flatlist the changes even though component did update called. From 08e20a9796d426ff16e46caed4ab262800b37774 Mon Sep 17 00:00:00 2001 From: Maciej Dobosz Date: Mon, 28 Aug 2023 15:00:23 +0200 Subject: [PATCH 023/386] Rewrite to functional component --- src/components/withLocalize.js | 68 +++++++++++++--------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index def7110c1b40..4064ace042c0 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -62,49 +62,28 @@ const localeProviderDefaultProps = { currentUserPersonalDetails: {}, }; -class LocaleContextProvider extends React.Component { - /** - * The context this component exposes to child components - * @returns {object} translation util functions and locale - */ - getContextValue() { - return { - translate: this.translate.bind(this), - numberFormat: this.numberFormat.bind(this), - datetimeToRelative: this.datetimeToRelative.bind(this), - datetimeToCalendarTime: this.datetimeToCalendarTime.bind(this), - formatPhoneNumber: this.formatPhoneNumber.bind(this), - fromLocaleDigit: this.fromLocaleDigit.bind(this), - toLocaleDigit: this.toLocaleDigit.bind(this), - preferredLocale: this.props.preferredLocale, - }; - } +function LocaleContextProvider ({children, currentUserPersonalDetails, preferredLocale}) { + const selectedTimezone = lodashGet(currentUserPersonalDetails, 'timezone.selected'); /** * @param {String} phrase * @param {Object} [variables] * @returns {String} */ - translate(phrase, variables) { - return Localize.translate(this.props.preferredLocale, phrase, variables); - } + const translate = (phrase, variables) => Localize.translate(preferredLocale, phrase, variables) /** * @param {Number} number * @param {Intl.NumberFormatOptions} options * @returns {String} */ - numberFormat(number, options) { - return NumberFormatUtils.format(this.props.preferredLocale, number, options); - } + const numberFormat = (number, options) => NumberFormatUtils.format(preferredLocale, number, options) /** * @param {String} datetime * @returns {String} */ - datetimeToRelative(datetime) { - return DateUtils.datetimeToRelative(this.props.preferredLocale, datetime); - } + const datetimeToRelative = (datetime) => DateUtils.datetimeToRelative(preferredLocale, datetime) /** * @param {String} datetime - ISO-formatted datetime string @@ -112,37 +91,42 @@ class LocaleContextProvider extends React.Component { * @param {Boolean} isLowercase * @returns {String} */ - datetimeToCalendarTime(datetime, includeTimezone, isLowercase = false) { - return DateUtils.datetimeToCalendarTime(this.props.preferredLocale, datetime, includeTimezone, lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected'), isLowercase); - } + const datetimeToCalendarTime = (datetime, includeTimezone, isLowercase = false) => DateUtils.datetimeToCalendarTime(preferredLocale, datetime, includeTimezone, selectedTimezone, isLowercase) /** * @param {String} phoneNumber * @returns {String} */ - formatPhoneNumber(phoneNumber) { - return LocalePhoneNumber.formatPhoneNumber(phoneNumber); - } + const formatPhoneNumber = (phoneNumber) => LocalePhoneNumber.formatPhoneNumber(phoneNumber) /** * @param {String} digit * @returns {String} */ - toLocaleDigit(digit) { - return LocaleDigitUtils.toLocaleDigit(this.props.preferredLocale, digit); - } - + const toLocaleDigit = (digit) => LocaleDigitUtils.toLocaleDigit(preferredLocale, digit) + /** * @param {String} localeDigit * @returns {String} */ - fromLocaleDigit(localeDigit) { - return LocaleDigitUtils.fromLocaleDigit(this.props.preferredLocale, localeDigit); - } + const fromLocaleDigit = (localeDigit) => LocaleDigitUtils.fromLocaleDigit(preferredLocale, localeDigit) - render() { - return {this.props.children}; - } + /** + * The context this component exposes to child components + * @returns {object} translation util functions and locale + */ + const getContextValue = () => ({ + translate, + numberFormat, + datetimeToRelative, + datetimeToCalendarTime, + formatPhoneNumber, + toLocaleDigit, + fromLocaleDigit, + preferredLocale, + }) + + return {children}; } LocaleContextProvider.propTypes = localeProviderPropTypes; From be7ea564639ef27e0ee8052c948d94f3119211b5 Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Mon, 28 Aug 2023 16:31:16 +0200 Subject: [PATCH 024/386] Linting --- src/pages/home/ReportScreenContext.js | 5 +---- src/pages/home/report/ReportActionItem.js | 2 +- src/pages/home/report/ReportActionsView.js | 5 ++--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/ReportScreenContext.js b/src/pages/home/ReportScreenContext.js index 0be1882699f4..1e8d30cf7585 100644 --- a/src/pages/home/ReportScreenContext.js +++ b/src/pages/home/ReportScreenContext.js @@ -3,7 +3,4 @@ import {createContext} from 'react'; const ActionListContext = createContext(); const ReactionListContext = createContext(); -export { - ActionListContext, - ReactionListContext -} \ No newline at end of file +export {ActionListContext, ReactionListContext}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 4a0c0d7bd3ba..fe2ea3acf691 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -64,7 +64,7 @@ import * as PersonalDetailsUtils from '../../../libs/PersonalDetailsUtils'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; import * as store from '../../../libs/actions/ReimbursementAccount/store'; import * as BankAccounts from '../../../libs/actions/BankAccounts'; -import { ReactionListContext } from '../ReportScreenContext'; +import {ReactionListContext} from '../ReportScreenContext'; import Permissions from '../../../libs/Permissions'; const propTypes = { diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 70eb65ade3ea..25d01a6953d1 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -19,7 +19,7 @@ import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import reportPropTypes from '../../reportPropTypes'; import PopoverReactionList from './ReactionList/PopoverReactionList'; import getIsReportFullyVisible from '../../../libs/getIsReportFullyVisible'; -import { ReactionListContext } from '../ReportScreenContext'; +import {ReactionListContext} from '../ReportScreenContext'; const propTypes = { /** The report currently being looked at */ @@ -54,9 +54,8 @@ const defaultProps = { }; function ReportActionsView(props) { - useCopySelectionHelper(); - const reactionListRef = useContext(ReactionListContext) + const reactionListRef = useContext(ReactionListContext); const didLayout = useRef(false); const didSubscribeToReportTypingEvents = useRef(false); const hasCachedActions = useRef(_.size(props.reportActions) > 0); From 43a729626bc2a3a252a83dadcc5c28e881e14fb1 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 14:55:28 -0700 Subject: [PATCH 025/386] add distance request consts --- src/CONST.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.js b/src/CONST.js index c78268aacd8b..53a7f14c75b5 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1309,6 +1309,7 @@ const CONST = { DATE: 'date', DESCRIPTION: 'description', MERCHANT: 'merchant', + DISTANCE: 'distance', }, FOOTER: { EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`, From 28c293e462f7d1b58d1bd5f1cdeb4d86955ccad5 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:03:53 -0700 Subject: [PATCH 026/386] add DISTANCE_REQUEST ONYXKEY --- src/ONYXKEYS.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d4d2ab1f90a6..78430e0fdf33 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -41,6 +41,9 @@ const ONYXKEYS = { // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & MoneyRequestPreview Components) IOU: 'iou', + // Contains loading data for the DistanceRequest components (MoneyRequestEditWaypointPage) + DISTANCE_REQUEST: 'distanceRequest', + /** Keeps track if there is modal currently visible or not */ MODAL: 'modal', From 322504aafb2a51ac8ffa998db3b12f24935371f9 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:04:17 -0700 Subject: [PATCH 027/386] add routes for editdistancerequest and editrequestwaypoint --- src/ROUTES.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ROUTES.js b/src/ROUTES.js index bf1beaecb3c3..df36bf3e5069 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -97,6 +97,7 @@ export default { MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', MONEY_REQUEST_DISTANCE_TAB: ':iouType/new/:reportID?/distance', MONEY_REQUEST_WAYPOINT: ':iouType/new/waypoint/:waypointIndex', + MONEY_REQUEST_EDIT_WAYPOINT: 'r/:threadReportID/edit/distance/waypoint/:waypointIndex', IOU_SEND_ADD_BANK_ACCOUNT: `${IOU_SEND}/add-bank-account`, IOU_SEND_ADD_DEBIT_CARD: `${IOU_SEND}/add-debit-card`, IOU_SEND_ENABLE_PAYMENTS: `${IOU_SEND}/enable-payments`, @@ -111,6 +112,7 @@ export default { getMoneyRequestMerchantRoute: (iouType, reportID = '') => `${iouType}/new/merchant/${reportID}`, getMoneyRequestDistanceTabRoute: (iouType, reportID = '') => `${iouType}/new/${reportID}/distance`, getMoneyRequestWaypointRoute: (iouType, waypointIndex) => `${iouType}/new/waypoint/${waypointIndex}`, + getMoneyRequestEditWaypointRoute: (threadReportID, waypointIndex) => `r/${threadReportID}/edit/distance/waypoint/${waypointIndex}`, SPLIT_BILL_DETAILS: `r/:reportID/split/:reportActionID`, getSplitBillDetailsRoute: (reportID, reportActionID) => `r/${reportID}/split/${reportActionID}`, getNewTaskRoute: (reportID) => `${NEW_TASK}/${reportID}`, From c5c72a5bba9430de145a99ca1ae2e4eb81dc789c Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:04:33 -0700 Subject: [PATCH 028/386] add distance translations --- src/languages/en.js | 1 + src/languages/es.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index 5dcb84c4e487..cdc96834289b 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -158,6 +158,7 @@ export default { category: 'Category', receipt: 'Receipt', replace: 'Replace', + distance: 'Distance', }, anonymousReportFooter: { logoTagline: 'Join the discussion.', diff --git a/src/languages/es.js b/src/languages/es.js index 9cb91261cdd5..35ac0c2cc9d8 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -157,6 +157,7 @@ export default { category: 'Categoría', receipt: 'Recibo', replace: 'Sustituir', + distance: 'Distancia', }, anonymousReportFooter: { logoTagline: 'Únete a la discusión.', From 85ac77daa978300b79c7e4386cc0c07f05327e9e Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:04:50 -0700 Subject: [PATCH 029/386] add editDistanceRequest actions --- src/libs/actions/IOU.js | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 838214bbd98e..3f64c2c7c756 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -530,6 +530,61 @@ function createDistanceRequest(report, payeeEmail, payeeAccountID, participant, ); } +/** + * Edits an existing distance request + * + * @param {String} transactionID + * @param {Object} transactionChanges + * @param {String} [transactionChanges.created] + * @param {Number} [transactionChanges.amount] + * @param {String} [transactionChanges.comment] + * @param {String} [transactionChanges.waypoints] + * + */ +function editDistanceRequest(transactionID, transactionThreadReportID, transactionChanges) { + const pendingFields = _.mapObject(updatedProperties, () => CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + const clearedPendingFields = _.mapObject(updatedProperties, () => null); + const errorFields = _.mapObject(pendingFields, () => ({ + [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.edit.genericError'), + })); + + const transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`]; + const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.parentReportID}`]; + const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); + const updatedTransaction = TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport); + API.write( + 'EditDistanceRequest', + {transactionID, ...ReportUtils.getTransactionDetails(updatedTransaction)}, + { + optimisticData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {...updatedProperties, pendingFields}, + }, + ], + successData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {pendingFields: clearedPendingFields}, + }, + ], + failureData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + pendingFields: clearedPendingFields, + errorFields, + }, + }, + ], + }, + ); +} + /** * Request money from another user * @@ -1767,6 +1822,13 @@ function createEmptyTransaction() { Onyx.merge(ONYXKEYS.IOU, {transactionID}); } +/** + * @param {String} transactionID + */ +function setDistanceRequestTransactionID(transactionID) { + Onyx.set(ONYXKEYS.DISTANCE_REQUEST, {transactionID}); +} + /** * Navigates to the next IOU page based on where the IOU request was started * @@ -1825,6 +1887,8 @@ export { setMoneyRequestMerchant, setMoneyRequestParticipants, setMoneyRequestReceipt, + setDistanceRequestTransactionID, createEmptyTransaction, navigateToNextPage, + editDistanceRequest, }; From 4cac0948a58821e3c9587b178eb82af91f657292 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:05:12 -0700 Subject: [PATCH 030/386] add new pages for editing distance requests --- src/pages/EditRequestDistancePage.js | 51 ++++++++++++++++++ src/pages/iou/MoneyRequestEditWaypointPage.js | 52 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/pages/EditRequestDistancePage.js create mode 100644 src/pages/iou/MoneyRequestEditWaypointPage.js diff --git a/src/pages/EditRequestDistancePage.js b/src/pages/EditRequestDistancePage.js new file mode 100644 index 000000000000..f2587db43a14 --- /dev/null +++ b/src/pages/EditRequestDistancePage.js @@ -0,0 +1,51 @@ +import React, {useEffect} from 'react'; +import PropTypes from 'prop-types'; +import ScreenWrapper from '../components/ScreenWrapper'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import Navigation from '../libs/Navigation/Navigation'; +import useLocalize from '../hooks/useLocalize'; +import DistanceRequest from '../components/DistanceRequest'; +import reportPropTypes from './reportPropTypes'; +import * as IOU from '../libs/actions/IOU'; + +const propTypes = { + /** The transactionID we're currently editing */ + transactionID: PropTypes.number, + + /** The report to with which the distance request is associated */ + report: reportPropTypes, +}; + +function EditRequestDistancePage({iou, report}) { + + useEffect(() => { + IOU.setDistanceRequestTransactionID(iou.transactionID); + }, []) + + const {translate} = useLocalize(); + return ( + + Navigation.goBack()} + /> + { + IOU.editDistanceRequest(iou.transactionID, report.reportID, {waypoints}); + Navigation.dismissModal(); + }} + /> + + ); +} + +EditRequestDistancePage.propTypes = propTypes; +EditRequestDistancePage.displayName = 'EditRequestDistancePage'; + +export default EditRequestDistancePage; diff --git a/src/pages/iou/MoneyRequestEditWaypointPage.js b/src/pages/iou/MoneyRequestEditWaypointPage.js new file mode 100644 index 000000000000..4a212cadd957 --- /dev/null +++ b/src/pages/iou/MoneyRequestEditWaypointPage.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import WaypointEditor from './WaypointEditor'; +import ONYXKEYS from '../../ONYXKEYS'; + +const propTypes = { + /** The transactionID of this request */ + transactionID: PropTypes.string, + + /** Route params */ + route: PropTypes.shape({ + params: PropTypes.shape({ + /** IOU type */ + iouType: PropTypes.string, + + /** Index of the waypoint being edited */ + waypointIndex: PropTypes.string, + }), + }), +}; + +const defaultProps = { + transactionID: '', + route: { + params: { + iouType: '', + waypointIndex: '', + }, + }, +}; + +// This component is responsible for grabbing the transactionID from the IOU key +// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that WaypointEditor can subscribe to the transaction. +function MoneyRequestEditWaypointPage({transactionID, route}) { + console.log(">>>> transactionID", transactionID); + return ( + + ); +} + +MoneyRequestEditWaypointPage.displayName = 'MoneyRequestEditWaypointPage'; +MoneyRequestEditWaypointPage.propTypes = propTypes; +MoneyRequestEditWaypointPage.defaultProps = defaultProps; +export default withOnyx({ + // We must provide a default value for transactionID here, otherwise the component won't mount + // because withOnyx returns null until all the keys are defined + transactionID: {key: ONYXKEYS.DISTANCE_REQUEST, selector: (distanceRequest) => (distanceRequest && distanceRequest.transactionID) || ''}, +})(MoneyRequestEditWaypointPage); From ec140606b87805a5dc28f94e5ca816f3dc0f729c Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 28 Aug 2023 16:05:44 -0700 Subject: [PATCH 031/386] simplify DistanceRequest to just take a transactionID, modify to handle edits --- src/components/DistanceRequest.js | 47 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index c96342dd8965..55975022866a 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -22,7 +22,6 @@ import useNetwork from '../hooks/useNetwork'; import useLocalize from '../hooks/useLocalize'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; -import * as IOU from '../libs/actions/IOU'; import participantPropTypes from './participantPropTypes'; import reportPropTypes from '../pages/reportPropTypes'; import transactionPropTypes from './transactionPropTypes'; @@ -38,17 +37,8 @@ const MAP_PADDING = 50; const DEFAULT_ZOOM_LEVEL = 10; const propTypes = { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: PropTypes.shape({ - id: PropTypes.string, - amount: PropTypes.number, - currency: PropTypes.string, - participants: PropTypes.arrayOf(participantPropTypes), - transactionID: PropTypes.string, - }), - - /** Type of money request (i.e. IOU) */ - iouType: PropTypes.string, + /** The transactionID of the distance request we're currently looking at */ + transactionID: PropTypes.number, /** The report to with which the distance request is associated */ report: reportPropTypes, @@ -64,6 +54,12 @@ const propTypes = { /** Time when the token will expire in ISO 8601 */ expiration: PropTypes.string, }), + + /** Are we editing an existing distance request, or creating a new one? */ + isEditingRequest: PropTypes.bool, + + /** Called on submit of this page */ + onSubmit: PropTypes.func.isRequired, }; const defaultProps = { @@ -71,15 +67,13 @@ const defaultProps = { id: '', amount: 0, currency: CONST.CURRENCY.USD, - participants: [], }, - iouType: '', - report: {}, transaction: {}, mapboxAccessToken: {}, + isEditingRequest: false, }; -function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) { +function DistanceRequest({transactionID, report, transaction, mapboxAccessToken, isEditingRequest, onSubmit}) { const [shouldShowGradient, setShouldShowGradient] = useState(false); const [scrollContainerHeight, setScrollContainerHeight] = useState(0); const [scrollContentHeight, setScrollContentHeight] = useState(0); @@ -136,12 +130,13 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) }, []); useEffect(() => { - if (!iou.transactionID || !_.isEmpty(waypoints)) { + console.log(">>>>", transactionID, waypoints); + if (!transactionID || !_.isEmpty(waypoints)) { return; } // Create the initial start and stop waypoints - Transaction.createInitialWaypoints(iou.transactionID); - }, [iou.transactionID, waypoints]); + Transaction.createInitialWaypoints(transactionID); + }, [transactionID, waypoints]); const updateGradientVisibility = (event = {}) => { // If a waypoint extends past the bottom of the visible area show the gradient, else hide it. @@ -155,8 +150,8 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) return; } - Transaction.getRoute(iou.transactionID, waypoints); - }, [shouldFetchRoute, iou.transactionID, waypoints]); + Transaction.getRoute(transactionID, waypoints); + }, [shouldFetchRoute, transactionID, waypoints]); useEffect(updateGradientVisibility, [scrollContainerHeight, scrollContentHeight]); @@ -196,7 +191,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) secondaryIcon={waypointIcon} secondaryIconFill={theme.icon} shouldShowRightIcon - onPress={() => Navigation.navigate(ROUTES.getMoneyRequestWaypointRoute('request', index))} + onPress={() => Navigation.navigate(isEditingRequest ? ROUTES.getMoneyRequestEditWaypointRoute(report.reportID, index) : ROUTES.getMoneyRequestWaypointRoute('request', index))} key={key} /> ); @@ -220,7 +215,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken})