From 9538cedff3d0323f84d17e6cb15d51ed1c7376ed Mon Sep 17 00:00:00 2001 From: OSBotify <76178356+OSBotify@users.noreply.github.com> Date: Thu, 14 Oct 2021 08:41:02 -0700 Subject: [PATCH 1/3] Merge pull request #5846 from Expensify/version-BUILD-daf81bcf39f720a6facb4823ca935b8354dc0949 (cherry picked from commit ba63b53a1b3bb29fb858dce49fdd64c4c5771754) --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 6a8f5a4d529..527cd7bd154 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001010716 - versionName "1.1.7-16" + versionCode 1001010718 + versionName "1.1.7-18" } splits { abi { diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 2edf5cebebd..741e3842010 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -31,7 +31,7 @@ CFBundleVersion - 1.1.7.16 + 1.1.7.18 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index b460baf60e0..3c93898f592 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.1.7.16 + 1.1.7.18 diff --git a/package-lock.json b/package-lock.json index 54356072678..c945b4eed14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.7-16", + "version": "1.1.7-18", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1aa63fc3d5c..b22d2cfd8d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.7-16", + "version": "1.1.7-18", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 7adf523707324d965bc912f5904f0ae2cf7e4e3c Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Oct 2021 08:14:14 -1000 Subject: [PATCH 2/3] Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition Add shouldCreateNewPolicy param to getPolicySummaries action (cherry picked from commit 30ce47c12e2d23d504db9a4f75c66dd0cc206c19) --- .../Navigation/AppNavigator/AuthScreens.js | 58 ++------ src/libs/Navigation/getPathName/index.js | 4 - .../Navigation/getPathName/index.native.js | 1 - src/libs/Navigation/linkingConfig.js | 1 - src/libs/actions/Policy.js | 134 +++++++++++++----- src/pages/LogInWithShortLivedTokenPage.js | 14 +- src/pages/workspace/WorkspaceNew.js | 16 --- 7 files changed, 119 insertions(+), 109 deletions(-) delete mode 100644 src/libs/Navigation/getPathName/index.js delete mode 100644 src/libs/Navigation/getPathName/index.native.js delete mode 100644 src/pages/workspace/WorkspaceNew.js diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index e25a0d76b53..10f22a0ca3c 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {Linking} from 'react-native'; import Onyx, {withOnyx} from 'react-native-onyx'; +import Str from 'expensify-common/lib/str'; import moment from 'moment'; import _ from 'underscore'; import lodashGet from 'lodash/get'; @@ -27,10 +29,9 @@ import Navigation from '../Navigation'; import * as User from '../../actions/User'; import {setModalVisibility} from '../../actions/Modal'; import NameValuePair from '../../actions/NameValuePair'; -import {getPolicySummaries, getPolicyList} from '../../actions/Policy'; +import {getPolicyList} from '../../actions/Policy'; import modalCardStyleInterpolator from './modalCardStyleInterpolator'; import createCustomModalStackNavigator from './createCustomModalStackNavigator'; -import Permissions from '../../Permissions'; import getOperatingSystem from '../../getOperatingSystem'; import {fetchFreePlanVerifiedBankAccount} from '../../actions/BankAccounts'; @@ -65,7 +66,6 @@ import defaultScreenOptions from './defaultScreenOptions'; import * as API from '../../API'; import {setLocale} from '../../actions/App'; import {cleanupSession} from '../../actions/Session'; -import WorkspaceNew from '../../../pages/workspace/WorkspaceNew'; Onyx.connect({ key: ONYXKEYS.MY_PERSONAL_DETAILS, @@ -107,22 +107,6 @@ const modalScreenListeners = { }, }; -let hasLoadedPolicies = false; - -/** - * We want to only load policy info if you are in the freePlan beta. - * @param {Array} betas - */ -function loadPoliciesBehindBeta(betas) { - // When removing the freePlan beta, simply load the policyList and the policySummaries in componentDidMount(). - // Policy info loading should not be blocked behind the defaultRooms beta alone. - if (!hasLoadedPolicies && (Permissions.canUseFreePlan(betas) || Permissions.canUseDefaultRooms(betas))) { - getPolicyList(); - getPolicySummaries(); - hasLoadedPolicies = true; - } -} - const propTypes = { /** Information about the network */ network: PropTypes.shape({ @@ -130,15 +114,11 @@ const propTypes = { isOffline: PropTypes.bool, }), - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - ...windowDimensionsPropTypes, }; const defaultProps = { network: {isOffline: true}, - betas: [], }; class AuthScreens extends React.Component { @@ -187,7 +167,14 @@ class AuthScreens extends React.Component { UnreadIndicatorUpdater.listenForReportChanges(); fetchFreePlanVerifiedBankAccount(); - loadPoliciesBehindBeta(this.props.betas); + // Load policies, maybe creating a new policy first. + Linking.getInitialURL() + .then((url) => { + const path = new URL(url).pathname; + const exitTo = new URLSearchParams(url).get('exitTo'); + const shouldCreateFreePolicy = Str.startsWith(path, Str.normalizeUrl(ROUTES.LOGIN_WITH_SHORT_LIVED_TOKEN)) && exitTo === ROUTES.WORKSPACE_NEW; + getPolicyList(shouldCreateFreePolicy); + }); // Refresh the personal details, timezone and betas every 30 minutes // There is no pusher event that sends updated personal details data yet @@ -223,19 +210,7 @@ class AuthScreens extends React.Component { } shouldComponentUpdate(nextProps) { - if (nextProps.isSmallScreenWidth !== this.props.isSmallScreenWidth) { - return true; - } - - if (nextProps.betas !== this.props.betas) { - return true; - } - - return false; - } - - componentDidUpdate() { - loadPoliciesBehindBeta(this.props.betas); + return nextProps.isSmallScreenWidth !== this.props.isSmallScreenWidth; } componentWillUnmount() { @@ -248,7 +223,6 @@ class AuthScreens extends React.Component { cleanupSession(); clearInterval(this.interval); this.interval = null; - hasLoadedPolicies = false; } render() { @@ -310,11 +284,6 @@ class AuthScreens extends React.Component { options={defaultScreenOptions} component={LogInWithShortLivedTokenPage} /> - {/* These are the various modal routes */} {/* Note: Each modal must have it's own stack navigator since we want to be able to navigate to any @@ -422,8 +391,5 @@ export default compose( network: { key: ONYXKEYS.NETWORK, }, - betas: { - key: ONYXKEYS.BETAS, - }, }), )(AuthScreens); diff --git a/src/libs/Navigation/getPathName/index.js b/src/libs/Navigation/getPathName/index.js deleted file mode 100644 index 161cece2a2e..00000000000 --- a/src/libs/Navigation/getPathName/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export default (initialUrl) => { - const initialURLObject = new URL(initialUrl); - return initialURLObject.pathname; -}; diff --git a/src/libs/Navigation/getPathName/index.native.js b/src/libs/Navigation/getPathName/index.native.js deleted file mode 100644 index 94bc2b70e39..00000000000 --- a/src/libs/Navigation/getPathName/index.native.js +++ /dev/null @@ -1 +0,0 @@ -export default () => ''; diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 1094048f0b9..a9a4fd1ceb9 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -33,7 +33,6 @@ export default { [SCREENS.LOGIN_WITH_VALIDATE_CODE_WORKSPACE_CARD]: ROUTES.LOGIN_WITH_VALIDATE_CODE_WORKSPACE_CARD, [SCREENS.LOGIN_WITH_VALIDATE_CODE_2FA_WORKSPACE_CARD]: ROUTES.LOGIN_WITH_VALIDATE_CODE_2FA_WORKSPACE_CARD, [SCREENS.LOG_IN_WITH_SHORT_LIVED_TOKEN]: ROUTES.LOGIN_WITH_SHORT_LIVED_TOKEN, - WorkspaceNew: ROUTES.WORKSPACE_NEW, // Modal Screens Settings: { diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 2dac2974a4c..23415f68b38 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -22,7 +22,23 @@ Onyx.connect({ }); /** - * Takes a full policy summary that is returned from the policySummaryList and simplifies it so we are only storing + * Simplifies the employeeList response into an object containing an array of emails + * + * @param {Object} employeeList + * @returns {Array} + */ +function getSimplifiedEmployeeList(employeeList) { + const employeeListEmails = _.chain(employeeList) + .pluck('email') + .flatten() + .unique() + .value(); + + return employeeListEmails; +} + +/** + * Takes a full policy that is returned from the policyList and simplifies it so we are only storing * the pieces of data that we need to in Onyx * * @param {Object} fullPolicy @@ -30,7 +46,10 @@ Onyx.connect({ * @param {String} fullPolicy.name * @param {String} fullPolicy.role * @param {String} fullPolicy.type +<<<<<<< HEAD * @param {String} fullPolicy.outputCurrency +======= +>>>>>>> 30ce47c12 (Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition) * @param {Object} fullPolicy.value.employeeList * @param {String} [fullPolicy.value.avatarURL] * @returns {Object} @@ -41,26 +60,24 @@ function getSimplifiedPolicyObject(fullPolicy) { name: fullPolicy.name, role: fullPolicy.role, type: fullPolicy.type, +<<<<<<< HEAD outputCurrency: fullPolicy.outputCurrency, +======= +>>>>>>> 30ce47c12 (Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition) employeeList: getSimplifiedEmployeeList(lodashGet(fullPolicy, 'value.employeeList')), avatarURL: lodashGet(fullPolicy, 'value.avatarURL', ''), }; } /** - * Simplifies the employeeList response into an object containing an array of emails - * - * @param {Object} employeeList - * @returns {Array} + * @param {Array} policyList + * @returns {Object} */ -function getSimplifiedEmployeeList(employeeList) { - const employeeListEmails = _.chain(employeeList) - .pluck('email') - .flatten() - .unique() - .value(); - - return employeeListEmails; +function transformPolicyListToOnyxCollection(policyList) { + return _.reduce(policyList, (memo, policy) => ({ + ...memo, + [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedPolicyObject(policy), + }), {}); } /** @@ -84,35 +101,82 @@ function updateAllPolicies(policyCollection) { } /** - * Fetches the policySummaryList from the API and saves a simplified version in Onyx + * Merges the passed in login into the specified policy + * + * @param {String} [name] + * @param {Boolean} [shouldAutomaticallyReroute] + * @returns {Promise} */ -function getPolicySummaries() { - API.GetPolicySummaryList() - .then((data) => { - if (data.jsonCode === 200) { - const policyDataToStore = _.reduce(data.policySummaryList, (memo, policy) => ({ - ...memo, - [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: getSimplifiedPolicyObject(policy), - }), {}); - updateAllPolicies(policyDataToStore); +function create(name = '', shouldAutomaticallyReroute = true) { + let res = null; + return API.Policy_Create({type: CONST.POLICY.TYPE.FREE, policyName: name}) + .then((response) => { + if (response.jsonCode !== 200) { + // Show the user feedback + const errorMessage = translateLocal('workspace.new.genericFailureMessage'); + Growl.error(errorMessage, 5000); + return; + } + res = response; + + // We are awaiting this merge so that we can guarantee our policy is available to any React components connected to the policies collection before we navigate to a new route. + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${response.policyID}`, { + employeeList: getSimplifiedEmployeeList(response.policy.employeeList), + id: response.policyID, + type: response.policy.type, + name: response.policy.name, + role: CONST.POLICY.ROLE.ADMIN, + }); + }).then(() => { + const policyID = lodashGet(res, 'policyID'); + if (shouldAutomaticallyReroute) { + Navigation.dismissModal(); + Navigation.navigate(policyID ? ROUTES.getWorkspaceCardRoute(policyID) : ROUTES.HOME); } + return Promise.resolve(policyID); }); } /** - * Fetches the policyList from the API and saves a simplified version in Onyx + * Fetches policy list from the API and saves a simplified version in Onyx, optionally creating a new policy first. + * + * More specifically, this action will: + * 1. Optionally create a new policy. + * 2. Fetch policy summaries. + * 3. Optionally navigate to the new policy. + * 4. Then fetch full policies. + * + * This way, we ensure that there's no race condition between creating the new policy and fetching existing ones, + * and we also don't have to wait for full policies to load before navigating to the new policy. + * + * @param {Boolean} [shouldCreateNewPolicy] */ -function getPolicyList() { - API.GetPolicyList() +function getPolicyList(shouldCreateNewPolicy = false) { + let newPolicyID; + const createPolicyPromise = shouldCreateNewPolicy + ? create('', false) + : Promise.resolve(); + createPolicyPromise + .then((policyID) => { + newPolicyID = policyID; + return API.GetPolicySummaryList(); + }) + .then((data) => { + if (data.jsonCode === 200) { + const policyDataToStore = transformPolicyListToOnyxCollection(data.policySummaryList || []); + updateAllPolicies(policyDataToStore); + } + + if (shouldCreateNewPolicy) { + Navigation.dismissModal(); + Navigation.navigate(newPolicyID ? ROUTES.getWorkspaceCardRoute(newPolicyID) : ROUTES.HOME); + } + + return API.GetPolicyList(); + }) .then((data) => { if (data.jsonCode === 200) { - const policyDataToStore = _.reduce(data.policyList, (memo, policy) => ({ - ...memo, - [`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`]: { - employeeList: getSimplifiedEmployeeList(policy.value.employeeList), - avatarURL: lodashGet(policy, 'value.avatarURL', ''), - }, - }), {}); + const policyDataToStore = transformPolicyListToOnyxCollection(data.policyList || []); updateAllPolicies(policyDataToStore); } }); @@ -219,6 +283,7 @@ function invite(logins, welcomeNote, policyID) { } /** +<<<<<<< HEAD * Merges the passed in login into the specified policy * * @param {String} [name] @@ -251,6 +316,8 @@ function create(name = '') { } /** +======= +>>>>>>> 30ce47c12 (Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition) * @param {Object} file * @returns {Promise} */ @@ -320,7 +387,6 @@ function hideWorkspaceAlertMessage(policyID) { } export { - getPolicySummaries, getPolicyList, removeMembers, invite, diff --git a/src/pages/LogInWithShortLivedTokenPage.js b/src/pages/LogInWithShortLivedTokenPage.js index 88265401f24..da341feb780 100644 --- a/src/pages/LogInWithShortLivedTokenPage.js +++ b/src/pages/LogInWithShortLivedTokenPage.js @@ -2,6 +2,7 @@ import React, {Component} from 'react'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; +import ROUTES from '../ROUTES'; import compose from '../libs/compose'; import ONYXKEYS from '../ONYXKEYS'; import {signInWithShortLivedToken} from '../libs/actions/Session'; @@ -52,11 +53,6 @@ const defaultProps = { }; class LogInWithShortLivedTokenPage extends Component { - constructor(props) { - super(props); - this.state = {hasRun: false}; - } - componentDidMount() { const accountID = parseInt(lodashGet(this.props.route.params, 'accountID', ''), 10); const email = lodashGet(this.props.route.params, 'email', ''); @@ -69,16 +65,20 @@ class LogInWithShortLivedTokenPage extends Component { } signInWithShortLivedToken(accountID, email, shortLivedToken, encryptedAuthToken); - this.setState({hasRun: true}); } componentDidUpdate() { - if (this.state.hasRun || !this.props.betas) { + const email = lodashGet(this.props.route.params, 'email', ''); + if (!this.props.betas || !this.props.session.authToken || email !== this.props.session.email) { return; } // exitTo is URI encoded because it could contain a variable number of slashes (i.e. "workspace/new" vs "workspace//card") const exitTo = decodeURIComponent(lodashGet(this.props.route.params, 'exitTo', '')); + if (exitTo === ROUTES.WORKSPACE_NEW) { + // New workspace creation is handled in AuthScreens, not in its own screen + return; + } // In order to navigate to a modal, we first have to dismiss the current modal. But there is no current // modal you say? I know, it confuses me too. Without dismissing the current modal, if the user cancels out diff --git a/src/pages/workspace/WorkspaceNew.js b/src/pages/workspace/WorkspaceNew.js deleted file mode 100644 index cbd987d4999..00000000000 --- a/src/pages/workspace/WorkspaceNew.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, {Component} from 'react'; -import * as Policy from '../../libs/actions/Policy'; -import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator'; - -class WorkspaceNew extends Component { - componentDidMount() { - // After the workspace is created, the user will automatically be directed to its settings page - Policy.create(); - } - - render() { - return ; - } -} - -export default WorkspaceNew; From 4644957649df8c794f78a60b2104d891237b67be Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Thu, 14 Oct 2021 09:48:35 -0600 Subject: [PATCH 3/3] Fix conflicts --- src/libs/actions/Policy.js | 41 -------------------------------------- 1 file changed, 41 deletions(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 23415f68b38..6f334ef1e87 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -46,10 +46,7 @@ function getSimplifiedEmployeeList(employeeList) { * @param {String} fullPolicy.name * @param {String} fullPolicy.role * @param {String} fullPolicy.type -<<<<<<< HEAD * @param {String} fullPolicy.outputCurrency -======= ->>>>>>> 30ce47c12 (Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition) * @param {Object} fullPolicy.value.employeeList * @param {String} [fullPolicy.value.avatarURL] * @returns {Object} @@ -60,10 +57,7 @@ function getSimplifiedPolicyObject(fullPolicy) { name: fullPolicy.name, role: fullPolicy.role, type: fullPolicy.type, -<<<<<<< HEAD outputCurrency: fullPolicy.outputCurrency, -======= ->>>>>>> 30ce47c12 (Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition) employeeList: getSimplifiedEmployeeList(lodashGet(fullPolicy, 'value.employeeList')), avatarURL: lodashGet(fullPolicy, 'value.avatarURL', ''), }; @@ -283,41 +277,6 @@ function invite(logins, welcomeNote, policyID) { } /** -<<<<<<< HEAD - * Merges the passed in login into the specified policy - * - * @param {String} [name] - */ -function create(name = '') { - let res = null; - API.Policy_Create({type: CONST.POLICY.TYPE.FREE, policyName: name}) - .then((response) => { - if (response.jsonCode !== 200) { - // Show the user feedback - const errorMessage = translateLocal('workspace.new.genericFailureMessage'); - Growl.error(errorMessage, 5000); - return; - } - res = response; - - // We are awaiting this merge so that we can guarantee our policy is available to any React components connected to the policies collection before we navigate to a new route. - return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${response.policyID}`, { - employeeList: getSimplifiedEmployeeList(response.policy.employeeList), - id: response.policyID, - type: response.policy.type, - name: response.policy.name, - role: CONST.POLICY.ROLE.ADMIN, - outputCurrency: response.policy.outputCurrency, - }); - }).then(() => { - Navigation.dismissModal(); - Navigation.navigate(ROUTES.getWorkspaceCardRoute(res.policyID)); - }); -} - -/** -======= ->>>>>>> 30ce47c12 (Merge pull request #5706 from Expensify/Rory-FixLoadingSpinnerRaceCondition) * @param {Object} file * @returns {Promise} */