From 051354f39a0525b679697bed59d7b8e88c1b80a7 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Tue, 12 Jul 2022 15:30:24 -0300 Subject: [PATCH 01/37] TMP - Mock get auth info so it fetches the info from the fake userPool instead (for testing purposes) --- src/utils/ssr/getAuthenticationInfo.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/ssr/getAuthenticationInfo.js b/src/utils/ssr/getAuthenticationInfo.js index e081ccf42f..b383afadb1 100644 --- a/src/utils/ssr/getAuthenticationInfo.js +++ b/src/utils/ssr/getAuthenticationInfo.js @@ -62,21 +62,22 @@ const getAuthenticationInfo = async () => { * because we do not have a way to reliably replicate Cognito in * local development. */ - const k8sEnv = process.env.K8S_ENV || 'staging'; - const userPoolName = `biomage-user-pool-case-insensitive-${k8sEnv}`; + // const k8sEnv = process.env.K8S_ENV || 'staging'; + // const userPoolName = `test-biomage-userpool-${k8sEnv}`; const identityPoolId = IdentityPools.find( - (pool) => pool.IdentityPoolName.includes(`${k8sEnv}-${sandboxId}`), + (pool) => pool.IdentityPoolName.includes('test-file-upload-identity-pool-staging-default'), + // (pool) => pool.IdentityPoolName.includes(`${k8sEnv}-${sandboxId}`), ).IdentityPoolId; - const userPoolId = UserPools.find((pool) => pool.Name === userPoolName).Id; + const userPoolId = UserPools.find((pool) => pool.Name === 'test-biomage-user-pool-staging').Id; const { UserPoolClients } = await userPoolClient.send( new ListUserPoolClientsCommand({ UserPoolId: userPoolId, MaxResults: 60 }), ); const userPoolClientId = UserPoolClients.find((client) => client.ClientName.includes( - `cluster-${sandboxId}`, + `test-biomage-cellscope-cluster-${sandboxId}`, )).ClientId; const [{ UserPoolClient: userPoolClientDetails }, { UserPool: { Domain } }] = await Promise.all([ From bb1135794a51d522a637a52352cc2be22539cdb0 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Tue, 12 Jul 2022 15:36:54 -0300 Subject: [PATCH 02/37] Add basic layout --- src/components/ContentWrapper.jsx | 13 ++-- .../PrivacyPolicyIntercept.jsx | 71 +++++++++++++++++++ src/pages/settings/profile/index.jsx | 27 ++++++- 3 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/components/data-management/PrivacyPolicyIntercept.jsx diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index fbdd09ff09..2358d93f8f 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -36,6 +36,7 @@ import Error from 'pages/_error'; import integrationTestConstants from 'utils/integrationTestConstants'; import pipelineStatus from 'utils/pipelineStatusValues'; import BrowserAlert from 'components/BrowserAlert'; +import PrivacyPolicyIntercept from './data-management/PrivacyPolicyIntercept'; const { Sider, Footer } = Layout; @@ -44,7 +45,7 @@ const { Paragraph, Text } = Typography; const ContentWrapper = (props) => { const dispatch = useDispatch(); - const [isAuth, setIsAuth] = useState(false); + const [user, setUser] = useState(null); const [collapsed, setCollapsed] = useState(false); const { routeExperimentId, experimentData, children } = props; @@ -139,14 +140,16 @@ const ContentWrapper = (props) => { useEffect(() => { Auth.currentAuthenticatedUser() - .then(() => setIsAuth(true)) + .then((currentUser) => { + setUser(currentUser); + }) .catch(() => { - setIsAuth(false); + setUser(null); Auth.federatedSignIn(); }); }, []); - if (!isAuth) return <>; + if (!user) return <>; const BigLogo = () => (
{ ); }; + return ( <> + {!user.attributes['custom:agreed_terms'] ? : <>} { + const { user } = props; + + const { + attributes: { + [agreedPrivacyPolicyKey]: originalAgreedPrivacyPolicy, + [agreedEmailsKey]: originalAgreedEmails, + }, + } = user; + + const [agreedPrivacyPolicy, setAgreedPrivacyPolicy] = useState(originalAgreedPrivacyPolicy); + const [agreedEmails, setAgreedEmails] = useState(originalAgreedEmails); + + const privacyPolicyUrl = 'https://static1.squarespace.com/static/5f355513fc75aa471d47455c/t/61f12e7b7266045b4cb137bc/1643196027265/Biomage_Privacy_Policy_Jan2022.pdf'; + + return ( + { }} + > + + In order to begin using the platform, blablabla + + + setAgreedPrivacyPolicy(e.target.checked.toString())} + /> + + I accept the terms of the + {' '} + Biomage privacy policy + . + + * + + + setAgreedEmails(e.target.checked.toString())} + /> + + I agree to receive updates about new features in Cellenics, + research done with Cellenics, and Cellenics community events. (No external marketing.) + + + + + ); +}; + +PrivacyPolicyIntercept.propTypes = { user: PropTypes.object.isRequired }; + +PrivacyPolicyIntercept.defaultProps = {}; + +export default PrivacyPolicyIntercept; diff --git a/src/pages/settings/profile/index.jsx b/src/pages/settings/profile/index.jsx index 29fef63b96..ccb976a4f2 100644 --- a/src/pages/settings/profile/index.jsx +++ b/src/pages/settings/profile/index.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import Auth from '@aws-amplify/auth'; import _ from 'lodash'; import { - Form, Input, Empty, Row, Col, Button, Space, + Form, Input, Empty, Row, Col, Button, Space, Checkbox, Typography, } from 'antd'; import { useRouter } from 'next/router'; import Header from 'components/Header'; @@ -10,6 +10,8 @@ import endUserMessages from 'utils/endUserMessages'; import pushNotificationMessage from 'utils/pushNotificationMessage'; import handleError from 'utils/http/handleError'; +const { Text } = Typography; + const ProfileSettings = () => { const router = useRouter(); @@ -39,16 +41,20 @@ const ProfileSettings = () => { currentUser(); }, []); + const agreedEmailsKey = 'custom:agreed_emails'; + const updateDetails = async () => { const { name, email } = changedUserAttributes; const { oldPassword, newPassword, confirmNewPassword } = changedPasswordAttributes; const invalidPasswordErrors = ['InvalidPasswordException', 'InvalidParameterException', 'NotAuthorizedException']; - if (name || email) { + if (name || email || changedUserAttributes[agreedEmailsKey]) { setEmailError(false); await Auth.updateUserAttributes(user, changedUserAttributes) .then(() => pushNotificationMessage('success', endUserMessages.ACCOUNT_DETAILS_UPDATED, 3)) - .catch(() => setEmailError(true)); + .catch(() => { + setEmailError(true); + }); } if (oldPassword || newPassword || confirmNewPassword) { setOldPasswordError(false); @@ -119,6 +125,21 @@ const ProfileSettings = () => { + + + setChanges({ + changedUserAttributes: { [agreedEmailsKey]: e.target.checked.toString() }, + })} + /> + + I agree to receive updates about new features in Cellenics, research done with Cellenics, and Cellenics community events. (No external marketing.) + + +

Password settings:

Date: Tue, 12 Jul 2022 17:29:42 -0300 Subject: [PATCH 03/37] Add functionality for PrivacyPolicyIntercept and connect it to cognito --- src/components/ContentWrapper.jsx | 8 +++++-- .../PrivacyPolicyIntercept.jsx | 24 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index 2358d93f8f..65b70a81dc 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -138,7 +138,7 @@ const ContentWrapper = (props) => { setGem2sRerunStatus(gem2sStatus); }, [gem2sBackendStatus, activeExperiment, samples, experiment]); - useEffect(() => { + const fetchCurrentUser = () => { Auth.currentAuthenticatedUser() .then((currentUser) => { setUser(currentUser); @@ -147,6 +147,10 @@ const ContentWrapper = (props) => { setUser(null); Auth.federatedSignIn(); }); + }; + + useEffect(() => { + fetchCurrentUser(); }, []); if (!user) return <>; @@ -336,7 +340,7 @@ const ContentWrapper = (props) => { return ( <> - {!user.attributes['custom:agreed_terms'] ? : <>} + {!user.attributes['custom:agreed_terms'] ? : <>} { - const { user } = props; + const { user, onOk } = props; const { attributes: { @@ -31,7 +35,21 @@ const PrivacyPolicyIntercept = (props) => { visible cancelButtonProps={{ style: { display: 'none' } }} okButtonProps={{ disabled: agreedPrivacyPolicy !== 'true' }} - onOk={() => { }} + closable={false} + onOk={async () => { + await Auth.updateUserAttributes( + user, + { + [agreedPrivacyPolicyKey]: agreedPrivacyPolicy, + [agreedEmailsKey]: agreedEmails, + }, + ) + .then(() => { + pushNotificationMessage('success', endUserMessages.ACCOUNT_DETAILS_UPDATED, 3); + onOk(); + }) + .catch(() => pushNotificationMessage('error', endUserMessages.ERROR_SAVING, 3)); + }} > In order to begin using the platform, blablabla From 0a828f2f8d5d58ade62a4f790db5cb4a816c85d3 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 13 Jul 2022 11:54:10 -0300 Subject: [PATCH 04/37] Some style updates to PrivacyPolicyIntercept --- src/components/data-management/PrivacyPolicyIntercept.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index c14b98be23..5add360da2 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -31,7 +31,7 @@ const PrivacyPolicyIntercept = (props) => { return ( { }} > - In order to begin using the platform, blablabla - setAgreedPrivacyPolicy(e.target.checked.toString())} /> + * I accept the terms of the {' '} Biomage privacy policy . - * Date: Wed, 13 Jul 2022 11:56:34 -0300 Subject: [PATCH 05/37] Minor fixes --- src/components/data-management/PrivacyPolicyIntercept.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index 5add360da2..7d577bddff 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -69,6 +69,7 @@ const PrivacyPolicyIntercept = (props) => { setAgreedEmails(e.target.checked.toString())} + style={{ marginRight: 10 }} /> I agree to receive updates about new features in Cellenics, @@ -80,7 +81,10 @@ const PrivacyPolicyIntercept = (props) => { ); }; -PrivacyPolicyIntercept.propTypes = { user: PropTypes.object.isRequired }; +PrivacyPolicyIntercept.propTypes = { + user: PropTypes.object.isRequired, + onOk: PropTypes.func.isRequired, +}; PrivacyPolicyIntercept.defaultProps = {}; From 7693e91b0e9dc6075aac85ea5a49afd964116230 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 13 Jul 2022 12:02:26 -0300 Subject: [PATCH 06/37] Center vertically modal --- src/components/data-management/PrivacyPolicyIntercept.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index 7d577bddff..fb806f685b 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -33,6 +33,7 @@ const PrivacyPolicyIntercept = (props) => { Date: Wed, 13 Jul 2022 13:50:23 -0300 Subject: [PATCH 07/37] Add logout button --- .../data-management/PrivacyPolicyIntercept.css | 4 ++++ .../data-management/PrivacyPolicyIntercept.jsx | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/components/data-management/PrivacyPolicyIntercept.css diff --git a/src/components/data-management/PrivacyPolicyIntercept.css b/src/components/data-management/PrivacyPolicyIntercept.css new file mode 100644 index 0000000000..35cba6c585 --- /dev/null +++ b/src/components/data-management/PrivacyPolicyIntercept.css @@ -0,0 +1,4 @@ +.ok-to-the-left-modal .ant-modal-confirm-btns .ant-btn{ + float: right; + margin-left: 10px; +} \ No newline at end of file diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index fb806f685b..f0196156e1 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -4,8 +4,11 @@ import PropTypes from 'prop-types'; import Auth from '@aws-amplify/auth'; import { - Modal, Space, Checkbox, Typography, + Modal, Space, Checkbox, Typography, Button, } from 'antd'; + +import 'components/data-management/PrivacyPolicyIntercept.css'; + import pushNotificationMessage from 'utils/pushNotificationMessage'; import endUserMessages from 'utils/endUserMessages'; @@ -25,7 +28,7 @@ const PrivacyPolicyIntercept = (props) => { } = user; const [agreedPrivacyPolicy, setAgreedPrivacyPolicy] = useState(originalAgreedPrivacyPolicy); - const [agreedEmails, setAgreedEmails] = useState(originalAgreedEmails); + const [agreedEmails, setAgreedEmails] = useState(originalAgreedEmails ?? 'false'); const privacyPolicyUrl = 'https://static1.squarespace.com/static/5f355513fc75aa471d47455c/t/61f12e7b7266045b4cb137bc/1643196027265/Biomage_Privacy_Policy_Jan2022.pdf'; @@ -34,7 +37,9 @@ const PrivacyPolicyIntercept = (props) => { title='Agree to the Biomage privacy policy to continue using Cellenics' visible centered - cancelButtonProps={{ style: { display: 'none' } }} + className='ok-to-the-left-modal' + cancelText='Sign out' + cancelButtonProps={{ danger: true }} okButtonProps={{ disabled: agreedPrivacyPolicy !== 'true' }} closable={false} onOk={async () => { @@ -51,6 +56,7 @@ const PrivacyPolicyIntercept = (props) => { }) .catch(() => pushNotificationMessage('error', endUserMessages.ERROR_SAVING, 3)); }} + onCancel={async () => Auth.signOut()} > From 48fbe73277a1c45c72a76f4a3339d1d0c6d439ae Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 13 Jul 2022 13:53:50 -0300 Subject: [PATCH 08/37] Fix alignment of checkbox on updates --- src/pages/settings/profile/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/profile/index.jsx b/src/pages/settings/profile/index.jsx index ccb976a4f2..274777ff29 100644 --- a/src/pages/settings/profile/index.jsx +++ b/src/pages/settings/profile/index.jsx @@ -126,9 +126,9 @@ const ProfileSettings = () => { - + setChanges({ From b00800f30234ebdd327ba5db635e32c62861ba11 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 13 Jul 2022 14:25:58 -0300 Subject: [PATCH 09/37] Add check on user attributes to decide whether to load data or not and move redux data to redux --- src/components/ContentWrapper.jsx | 27 ++++++++----------------- src/pages/data-management/index.jsx | 10 ++++++--- src/pages/settings/profile/index.jsx | 20 +++++++++--------- src/redux/actionTypes/user.js | 11 ++++++++++ src/redux/actions/user/index.js | 6 ++++++ src/redux/actions/user/loadUser.js | 17 ++++++++++++++++ src/redux/reducers/index.js | 7 +++++-- src/redux/reducers/user/index.js | 20 ++++++++++++++++++ src/redux/reducers/user/initialState.js | 5 +++++ src/redux/reducers/user/userLoaded.js | 9 +++++++++ 10 files changed, 97 insertions(+), 35 deletions(-) create mode 100644 src/redux/actionTypes/user.js create mode 100644 src/redux/actions/user/index.js create mode 100644 src/redux/actions/user/loadUser.js create mode 100644 src/redux/reducers/user/index.js create mode 100644 src/redux/reducers/user/initialState.js create mode 100644 src/redux/reducers/user/userLoaded.js diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index 65b70a81dc..3369be2a7c 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -17,8 +17,6 @@ import { } from 'antd'; import { modules } from 'utils/constants'; -import Auth from '@aws-amplify/auth'; - import { useAppRouter } from 'utils/AppRouteProvider'; import calculateGem2sRerunStatus from 'utils/data-management/calculateGem2sRerunStatus'; @@ -36,16 +34,15 @@ import Error from 'pages/_error'; import integrationTestConstants from 'utils/integrationTestConstants'; import pipelineStatus from 'utils/pipelineStatusValues'; import BrowserAlert from 'components/BrowserAlert'; +import { loadUser } from 'redux/actions/user'; import PrivacyPolicyIntercept from './data-management/PrivacyPolicyIntercept'; -const { Sider, Footer } = Layout; - -const { Paragraph, Text } = Typography; +const { Sider } = Layout; +const { Text } = Typography; const ContentWrapper = (props) => { const dispatch = useDispatch(); - const [user, setUser] = useState(null); const [collapsed, setCollapsed] = useState(false); const { routeExperimentId, experimentData, children } = props; @@ -54,6 +51,7 @@ const ContentWrapper = (props) => { const currentExperimentIdRef = useRef(routeExperimentId); const activeExperimentId = useSelector((state) => state?.experiments?.meta?.activeExperimentId); const activeExperiment = useSelector((state) => state.experiments[activeExperimentId]); + const user = useSelector((state) => state.user.current); const samples = useSelector((state) => state.samples); @@ -138,19 +136,8 @@ const ContentWrapper = (props) => { setGem2sRerunStatus(gem2sStatus); }, [gem2sBackendStatus, activeExperiment, samples, experiment]); - const fetchCurrentUser = () => { - Auth.currentAuthenticatedUser() - .then((currentUser) => { - setUser(currentUser); - }) - .catch(() => { - setUser(null); - Auth.federatedSignIn(); - }); - }; - useEffect(() => { - fetchCurrentUser(); + dispatch(loadUser()); }, []); if (!user) return <>; @@ -338,9 +325,11 @@ const ContentWrapper = (props) => { ); }; + if (!user) return <>; + return ( <> - {!user.attributes['custom:agreed_terms'] ? : <>} + {user?.attributes['custom:agreed_terms'] !== 'true' ? dispatch(loadUser())} /> : <>} { const { activeExperimentId } = useSelector((state) => state.experiments.meta); const experiments = useSelector(((state) => state.experiments)); + const user = useSelector((state) => state.user.current); const activeExperiment = experiments[activeExperimentId]; const { saving: experimentsSaving } = experiments.meta; @@ -31,8 +33,10 @@ const DataManagementPage = () => { const [newProjectModalVisible, setNewProjectModalVisible] = useState(false); useEffect(() => { + if (user?.attributes['custom:agreed_terms'] !== 'true') return; + if (experiments.ids.length === 0) dispatch(loadExperiments()); - }, []); + }, [user]); const samplesAreLoaded = () => { const loadedSampleIds = Object.keys(samples); @@ -40,14 +44,14 @@ const DataManagementPage = () => { }; useEffect(() => { - if (!activeExperimentId) return; + if (!activeExperimentId || user?.attributes['custom:agreed_terms'] !== 'true') return; dispatch(loadProcessingSettings(activeExperimentId)); if (!samplesAreLoaded()) dispatch(loadSamples(activeExperimentId)); dispatch(loadBackendStatus(activeExperimentId)); - }, [activeExperimentId]); + }, [activeExperimentId, user]); const PROJECTS_LIST = 'Projects'; const PROJECT_DETAILS = 'Project Details'; diff --git a/src/pages/settings/profile/index.jsx b/src/pages/settings/profile/index.jsx index 274777ff29..ab2719d3c5 100644 --- a/src/pages/settings/profile/index.jsx +++ b/src/pages/settings/profile/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import Auth from '@aws-amplify/auth'; import _ from 'lodash'; import { @@ -9,13 +9,18 @@ import Header from 'components/Header'; import endUserMessages from 'utils/endUserMessages'; import pushNotificationMessage from 'utils/pushNotificationMessage'; import handleError from 'utils/http/handleError'; +import { useSelector, useDispatch } from 'react-redux'; +import { loadUser } from 'redux/actions/user'; const { Text } = Typography; const ProfileSettings = () => { const router = useRouter(); - const [user, setUser] = useState(); + const dispatch = useDispatch(); + + const user = useSelector((state) => state.user.current); + const [oldPasswordError, setOldPasswordError] = useState(null); const [newPasswordError, setNewPasswordError] = useState(null); const [emailError, setEmailError] = useState(null); @@ -33,14 +38,6 @@ const ProfileSettings = () => { setNewAttributes(newChanges); }; - const currentUser = () => Auth.currentAuthenticatedUser() - .then((userData) => setUser(userData)) - .catch((e) => console.log('error during getuser', e)); - - useEffect(() => { - currentUser(); - }, []); - const agreedEmailsKey = 'custom:agreed_emails'; const updateDetails = async () => { @@ -81,7 +78,8 @@ const ProfileSettings = () => { }); } } - currentUser(); + + dispatch(loadUser()); setChanges(initialState); }; diff --git a/src/redux/actionTypes/user.js b/src/redux/actionTypes/user.js new file mode 100644 index 0000000000..8f78846ac0 --- /dev/null +++ b/src/redux/actionTypes/user.js @@ -0,0 +1,11 @@ +const USER = 'user'; + +/** + * User was loaded + */ +const USER_LOADED = `${USER}/loaded`; + +export { + // eslint-disable-next-line import/prefer-default-export + USER_LOADED, +}; diff --git a/src/redux/actions/user/index.js b/src/redux/actions/user/index.js new file mode 100644 index 0000000000..1fa37f482b --- /dev/null +++ b/src/redux/actions/user/index.js @@ -0,0 +1,6 @@ +import loadUser from './loadUser'; + +export { + // eslint-disable-next-line import/prefer-default-export + loadUser, +}; diff --git a/src/redux/actions/user/loadUser.js b/src/redux/actions/user/loadUser.js new file mode 100644 index 0000000000..f7847b3318 --- /dev/null +++ b/src/redux/actions/user/loadUser.js @@ -0,0 +1,17 @@ +import Auth from '@aws-amplify/auth'; +import { USER_LOADED } from 'redux/actionTypes/user'; + +const loadUser = () => async (dispatch) => { + try { + const user = await Auth.currentAuthenticatedUser(); + + dispatch({ + type: USER_LOADED, + payload: { user }, + }); + } catch (e) { + Auth.federatedSignIn(); + } +}; + +export default loadUser; diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js index 40fed75593..63865496a0 100644 --- a/src/redux/reducers/index.js +++ b/src/redux/reducers/index.js @@ -11,8 +11,9 @@ import experimentSettingsReducer from './experimentSettings'; import genesReducer from './genes'; import layoutReducer from './layout'; import sampleReducer from './samples'; -import networkResourcesReducer from './networkResources'; import backendStatusReducer from './backendStatus'; +import networkResourcesReducer from './networkResources'; +import userReducer from './user'; import { EXPERIMENTS_SWITCH } from '../actionTypes/experiments'; const appReducers = combineReducers({ @@ -29,6 +30,7 @@ const appReducers = combineReducers({ layout: layoutReducer, samples: sampleReducer, networkResources: networkResourcesReducer, + user: userReducer, }); const rootReducer = (state, action) => { @@ -36,11 +38,12 @@ const rootReducer = (state, action) => { if (action.type === EXPERIMENTS_SWITCH) { // we need to keep the old state for these parts of the store newState = { - networkResources: state.networkResources, samples: state.samples, projects: state.projects, backendStatus: state.backendStatus, experiments: state.experiments, + networkResources: state.networkResources, + userReducer: state.user, }; } return appReducers(newState, action); diff --git a/src/redux/reducers/user/index.js b/src/redux/reducers/user/index.js new file mode 100644 index 0000000000..3ee7a8952d --- /dev/null +++ b/src/redux/reducers/user/index.js @@ -0,0 +1,20 @@ +import { + USER_LOADED, +} from 'redux/actionTypes/user'; + +import initialState from 'redux/reducers/user/initialState'; +import userLoaded from 'redux/reducers/user/userLoaded'; + +const userReducer = (state = initialState, action) => { + switch (action.type) { + case USER_LOADED: { + return userLoaded(state, action); + } + + default: { + return state; + } + } +}; + +export default userReducer; diff --git a/src/redux/reducers/user/initialState.js b/src/redux/reducers/user/initialState.js new file mode 100644 index 0000000000..4ca54b3c68 --- /dev/null +++ b/src/redux/reducers/user/initialState.js @@ -0,0 +1,5 @@ +const initialState = { + current: null, +}; + +export default initialState; diff --git a/src/redux/reducers/user/userLoaded.js b/src/redux/reducers/user/userLoaded.js new file mode 100644 index 0000000000..584aac5198 --- /dev/null +++ b/src/redux/reducers/user/userLoaded.js @@ -0,0 +1,9 @@ +/* eslint-disable no-param-reassign */ +import produce from 'immer'; + +const userLoaded = produce((draft, action) => { + const { user } = action.payload; + draft.current = user; +}); + +export default userLoaded; From 934a5ff8a1edc22d4af0d5bf7ebeb697c07ff5a3 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Wed, 13 Jul 2022 16:42:34 -0300 Subject: [PATCH 10/37] Use user from selector in UserButton --- src/components/header/UserButton.jsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/header/UserButton.jsx b/src/components/header/UserButton.jsx index a64c307f7e..f31793940c 100644 --- a/src/components/header/UserButton.jsx +++ b/src/components/header/UserButton.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Avatar, Button, @@ -11,24 +11,23 @@ import Auth from '@aws-amplify/auth'; import endUserMessages from 'utils/endUserMessages'; import { resetTrackingId } from 'utils/tracking'; import handleError from 'utils/http/handleError'; +import { loadUser } from 'redux/actions/user'; +import { useDispatch, useSelector } from 'react-redux'; const UserButton = () => { - const [user, setUser] = useState(); + const dispatch = useDispatch(); - const getUser = () => Auth.currentAuthenticatedUser() - .then((userData) => userData) - .catch((e) => console.log('error during getuser', e)); + const user = useSelector((state) => state.user.current); useEffect(() => { Hub.listen('auth', ({ payload: { event } }) => { switch (event) { case 'signIn': case 'cognitoHostedUI': - getUser().then((userData) => setUser(userData)); + dispatch(loadUser()); break; case 'signOut': resetTrackingId(); - setUser(null); break; case 'signIn_failure': case 'cognitoHostedUI_failure': @@ -38,13 +37,11 @@ const UserButton = () => { break; } }); - - getUser().then((userData) => setUser(userData)); }, []); const content = () => ( - + Your profile From a0f2f84aa48677fb232108c213d129e73256c7bd Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 11:22:19 -0300 Subject: [PATCH 11/37] Some cleanup --- src/components/ContentWrapper.jsx | 3 +++ src/components/data-management/ExampleExperimentsSpace.jsx | 5 +++-- src/components/data-management/PrivacyPolicyIntercept.css | 2 +- src/components/data-management/PrivacyPolicyIntercept.jsx | 4 ++-- src/pages/data-management/index.jsx | 1 - 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index 3369be2a7c..5071e7c3fc 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -142,6 +142,9 @@ const ContentWrapper = (props) => { if (!user) return <>; + console.log('userDebug'); + console.log(user); + const BigLogo = () => (
{ const dispatch = useDispatch(); const environment = useSelector((state) => state?.networkResources?.environment); + const user = useSelector((state) => state?.user?.current); const [exampleExperiments, setExampleExperiments] = useState([]); useEffect(() => { - if (!environment) return; + if (!environment || user?.attributes['custom:agreed_terms'] !== 'true') return; fetchAPI('/v2/experiments/examples').then((experiments) => { setExampleExperiments(experiments); }).catch(() => { }); - }, [environment]); + }, [environment, user]); const cloneIntoCurrentExperiment = async (exampleExperimentId) => { const url = `/v2/experiments/${exampleExperimentId}/clone`; diff --git a/src/components/data-management/PrivacyPolicyIntercept.css b/src/components/data-management/PrivacyPolicyIntercept.css index 35cba6c585..5103d2fd38 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.css +++ b/src/components/data-management/PrivacyPolicyIntercept.css @@ -1,4 +1,4 @@ -.ok-to-the-left-modal .ant-modal-confirm-btns .ant-btn{ +.ok-to-the-right-modal .ant-modal-confirm-btns .ant-btn { float: right; margin-left: 10px; } \ No newline at end of file diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index f0196156e1..a166127f21 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import Auth from '@aws-amplify/auth'; import { - Modal, Space, Checkbox, Typography, Button, + Modal, Space, Checkbox, Typography, } from 'antd'; import 'components/data-management/PrivacyPolicyIntercept.css'; @@ -37,7 +37,7 @@ const PrivacyPolicyIntercept = (props) => { title='Agree to the Biomage privacy policy to continue using Cellenics' visible centered - className='ok-to-the-left-modal' + className='ok-to-the-right-modal' cancelText='Sign out' cancelButtonProps={{ danger: true }} okButtonProps={{ disabled: agreedPrivacyPolicy !== 'true' }} diff --git a/src/pages/data-management/index.jsx b/src/pages/data-management/index.jsx index cf2f380147..3714e06720 100644 --- a/src/pages/data-management/index.jsx +++ b/src/pages/data-management/index.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import Auth from '@aws-amplify/auth'; import { Space } from 'antd'; From eb00fa5f90193262c78b52259e9b47c9aada8d9e Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 11:23:20 -0300 Subject: [PATCH 12/37] Remove console log --- src/components/ContentWrapper.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index 5071e7c3fc..3369be2a7c 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -142,9 +142,6 @@ const ContentWrapper = (props) => { if (!user) return <>; - console.log('userDebug'); - console.log(user); - const BigLogo = () => (
Date: Thu, 14 Jul 2022 11:27:59 -0300 Subject: [PATCH 13/37] Fix contente wrappe tests --- src/__test__/components/ContentWrapper.test.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/__test__/components/ContentWrapper.test.jsx b/src/__test__/components/ContentWrapper.test.jsx index 08671a2d6d..6fffea04f4 100644 --- a/src/__test__/components/ContentWrapper.test.jsx +++ b/src/__test__/components/ContentWrapper.test.jsx @@ -36,7 +36,11 @@ jest.mock('next/router', () => ({ })); jest.mock('@aws-amplify/auth', () => ({ - currentAuthenticatedUser: jest.fn().mockImplementation(async () => true), + currentAuthenticatedUser: jest.fn().mockImplementation(async () => ({ + attributes: { + 'custom:agreed_terms': 'true', + }, + })), federatedSignIn: jest.fn(), })); @@ -124,7 +128,6 @@ describe('ContentWrapper', () => { await store.dispatch(updateExperimentInfo({ experimentId, experimentName, sampleIds })); }); - // PROBLEMATIC it('renders correctly', async () => { getBackendStatus.mockImplementation(() => () => ({ loading: false, From e030491b15f2b492c7ae98aaead25bc5649f2b3b Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 11:38:27 -0300 Subject: [PATCH 14/37] Fix SamplesTable tests --- .../components/data-management/SamplesTable.test.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/__test__/components/data-management/SamplesTable.test.jsx b/src/__test__/components/data-management/SamplesTable.test.jsx index 90d83dc85a..bc4fc05c0d 100644 --- a/src/__test__/components/data-management/SamplesTable.test.jsx +++ b/src/__test__/components/data-management/SamplesTable.test.jsx @@ -23,9 +23,15 @@ import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; import { loadSamples } from 'redux/actions/samples'; import mockDemoExperiments from '__test__/test-utils/mockData/mockDemoExperiments.json'; +import { loadUser } from 'redux/actions/user'; jest.mock('@aws-amplify/auth', () => ({ - currentAuthenticatedUser: jest.fn(() => Promise.resolve({ attributes: { name: 'mockUserName' } })), + currentAuthenticatedUser: jest.fn(() => Promise.resolve({ + attributes: { + name: 'mockUserName', + 'custom:agreed_terms': 'true', + }, + })), federatedSignIn: jest.fn(), })); @@ -104,6 +110,8 @@ describe('Samples table', () => { // Defaults to project with samples await storeState.dispatch(setActiveExperiment(experimentWithSamplesId)); await storeState.dispatch(loadEnvironment('test')); + + await storeState.dispatch(loadUser()); }); it('Does not show prompt to upload datasets if samples are available', async () => { From cd745de41fdb18f646e8e868db3b976e4e30ee5a Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 11:53:16 -0300 Subject: [PATCH 15/37] Fix index of data-management --- .../data-management/index.test.jsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx b/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx index 412dba3f34..b5d9624368 100644 --- a/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx +++ b/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import _ from 'lodash'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'; import { Provider } from 'react-redux'; @@ -21,6 +21,7 @@ import userEvent from '@testing-library/user-event'; import { setActiveExperiment } from 'redux/actions/experiments'; import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; +import { loadUser } from 'redux/actions/user'; jest.mock('utils/data-management/downloadFromUrl'); jest.mock('react-resize-detector', () => (props) => props.children({ width: 100, height: 100 })); @@ -34,7 +35,12 @@ jest.mock('utils/AppRouteProvider', () => ({ })); jest.mock('@aws-amplify/auth', () => ({ - currentAuthenticatedUser: jest.fn(() => Promise.resolve({ attributes: { name: 'mockUserName' } })), + currentAuthenticatedUser: jest.fn(() => Promise.resolve({ + attributes: { + name: 'mockUserName', + 'custom:agreed_terms': 'true', + }, + })), federatedSignIn: jest.fn(), })); @@ -53,11 +59,6 @@ const experimentWithoutSamples = experiments.find( (experiment) => experiment.samplesOrder.length === 0, ); -const expectedSampleNames = [ - 'Example 1', - 'Another-Example no.2', -]; - const experimentWithSamplesId = experimentWithSamples.id; const experimentWithoutSamplesId = experimentWithoutSamples.id; @@ -80,6 +81,8 @@ describe('Data Management page', () => { storeState = makeStore(); storeState.dispatch(loadEnvironment('test')); + + storeState.dispatch(loadUser()); }); it('Shows an empty project list if there are no projects', async () => { From 11c4a6fa467ff8d441b68e98d7ef60a647551ad6 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 13:20:10 -0300 Subject: [PATCH 16/37] Update snapshots --- src/__test__/pages/__snapshots__/_error.test.jsx.snap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/__test__/pages/__snapshots__/_error.test.jsx.snap b/src/__test__/pages/__snapshots__/_error.test.jsx.snap index aa4ec46aab..5a4050c955 100644 --- a/src/__test__/pages/__snapshots__/_error.test.jsx.snap +++ b/src/__test__/pages/__snapshots__/_error.test.jsx.snap @@ -143,6 +143,9 @@ Array [ "saving": false, }, }, + "user": Object { + "current": null, + }, }, ] `; @@ -290,6 +293,9 @@ Array [ "saving": false, }, }, + "user": Object { + "current": null, + }, }, ] `; From de60c27181e007d332a96ffcd6565152316eae15 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 14:01:40 -0300 Subject: [PATCH 17/37] Fix profile test --- .../data-management/index.test.jsx | 1 - .../pages/settings/profile/index.test.jsx | 47 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx b/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx index b5d9624368..5b7c5c1776 100644 --- a/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx +++ b/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx @@ -81,7 +81,6 @@ describe('Data Management page', () => { storeState = makeStore(); storeState.dispatch(loadEnvironment('test')); - storeState.dispatch(loadUser()); }); diff --git a/src/__test__/pages/settings/profile/index.test.jsx b/src/__test__/pages/settings/profile/index.test.jsx index da30703dee..76fc404c29 100644 --- a/src/__test__/pages/settings/profile/index.test.jsx +++ b/src/__test__/pages/settings/profile/index.test.jsx @@ -4,10 +4,15 @@ import { render, screen } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import userEvent from '@testing-library/user-event'; import { useRouter } from 'next/router'; +import { makeStore } from 'redux/store'; import createTestComponentFactory from '__test__/test-utils/testComponentFactory'; import Auth from '@aws-amplify/auth'; import ProfileSettings from 'pages/settings/profile'; import pushNotificationMessage from 'utils/pushNotificationMessage'; +import { Provider } from 'react-redux'; + +import { loadUser } from 'redux/actions/user'; +import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; jest.mock('next/router', () => ({ useRouter: jest.fn(), @@ -16,19 +21,43 @@ jest.mock('next/router', () => ({ jest.mock('@aws-amplify/auth', () => jest.fn()); jest.mock('utils/pushNotificationMessage'); -const profileSettingsPageFactory = createTestComponentFactory(ProfileSettings); const updateMock = jest.fn(() => Promise.resolve(true)); +const profileSettingsPageFactory = createTestComponentFactory(ProfileSettings); + +const renderProfileSettingsPage = (store, newState = {}) => { + render( + + {profileSettingsPageFactory(newState)} + , + ); +}; + +const setUpAuthMocks = () => { + Auth.currentAuthenticatedUser = jest.fn(() => Promise.resolve({ + attributes: { + name: userName, + 'custom:agreed_terms': 'true', + }, + })); + Auth.signOut = jest.fn(() => { }); + Auth.federatedSignIn = jest.fn(() => { }); + Auth.updateUserAttributes = updateMock; +}; + const userName = 'Arthur Dent'; jest.mock('components/Header', () => () => <>); describe('Profile page', () => { + const store = makeStore(); + beforeEach(async () => { jest.clearAllMocks(); - Auth.currentAuthenticatedUser = jest.fn(() => Promise.resolve({ attributes: { name: userName } })); - Auth.signOut = jest.fn(() => { }); - Auth.federatedSignIn = jest.fn(() => { }); - Auth.updateUserAttributes = updateMock; + + setUpAuthMocks(); + + store.dispatch(loadEnvironment('test')); + store.dispatch(loadUser()); }); it('check that the back button is called on cancel', async () => { @@ -38,9 +67,7 @@ describe('Profile page', () => { })); await act(async () => { - render( - profileSettingsPageFactory(), - ); + renderProfileSettingsPage(store); }); await act(async () => { @@ -52,9 +79,7 @@ describe('Profile page', () => { it('check update is called on Save changes', async () => { await act(async () => { - render( - profileSettingsPageFactory(), - ); + renderProfileSettingsPage(store); }); const nameInput = screen.getByPlaceholderText(userName); From d0dadc6a217a09aaf3456be1d3e19bdc73e25d02 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 14:10:26 -0300 Subject: [PATCH 18/37] Fix userButton tests --- .../components/header/UserButton.test.jsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/__test__/components/header/UserButton.test.jsx b/src/__test__/components/header/UserButton.test.jsx index 39fd2b6716..2a81fe8582 100644 --- a/src/__test__/components/header/UserButton.test.jsx +++ b/src/__test__/components/header/UserButton.test.jsx @@ -1,3 +1,5 @@ +import React from 'react'; + import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { act } from 'react-dom/test-utils'; @@ -6,12 +8,19 @@ import Auth from '@aws-amplify/auth'; import UserButton from 'components/header/UserButton'; import createTestComponentFactory from '__test__/test-utils/testComponentFactory'; +import { Provider } from 'react-redux'; +import { makeStore } from 'redux/store'; +import { loadUser } from 'redux/actions/user'; const UserButtonFactory = createTestComponentFactory(UserButton); -const renderUserButton = async () => { +const renderUserButton = async (store) => { await act(async () => { - render(UserButtonFactory({})); + render( + + {UserButtonFactory(store)} + , + ); }); }; @@ -26,18 +35,25 @@ const getLoginButton = () => { }; describe('UserButton', () => { + let store; + beforeEach(async () => { jest.clearAllMocks(); - Auth.currentAuthenticatedUser = jest.fn(() => Promise.resolve({ attributes: { name: userName } })); + store = makeStore(); + + Auth.currentAuthenticatedUser = jest.fn(() => Promise.resolve({ attributes: { name: userName, 'custom:agreed_terms': 'true' } })); Auth.signOut = jest.fn(() => { }); Auth.federatedSignIn = jest.fn(() => { }); + + store.dispatch(loadUser()); }); it('Shows sign in by default', async () => { Auth.currentAuthenticatedUser = jest.fn(() => Promise.resolve(null)); + store.dispatch(loadUser()); - await renderUserButton(); + await renderUserButton(store); expect(screen.getByText(/Sign in/i)).toBeInTheDocument(); }); @@ -45,13 +61,13 @@ describe('UserButton', () => { it('Shows the user initial for the ', async () => { const userInitial = getUserInitial(); - await renderUserButton(); + await renderUserButton(store); expect(screen.getByText(userInitial)).toBeInTheDocument(); }); it('Clicking on menu opens up the menu bar', async () => { - await renderUserButton(); + await renderUserButton(store); const button = getLoginButton(); From c4b5f9af87390928e39acc87a7d6056fcfa39a81 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Thu, 14 Jul 2022 14:15:31 -0300 Subject: [PATCH 19/37] Update snapshots --- .../experiments/__snapshots__/switchExperiment.test.js.snap | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap b/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap index 02b43dc3d0..be72f2c042 100644 --- a/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap +++ b/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap @@ -321,6 +321,9 @@ Object { "uuid": "test9188-d682-test-mock-cb6d644cmock-2", }, }, + "user": Object { + "current": null, + }, } `; @@ -645,5 +648,8 @@ Object { "uuid": "test9188-d682-test-mock-cb6d644cmock-2", }, }, + "user": Object { + "current": null, + }, } `; From 09fc0e8de6925aaa27e4148e2fcbe947f9cfb612 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 08:35:02 -0300 Subject: [PATCH 20/37] Fix based on comment --- src/components/ContentWrapper.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index 3369be2a7c..ff5c1f0791 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -329,7 +329,7 @@ const ContentWrapper = (props) => { return ( <> - {user?.attributes['custom:agreed_terms'] !== 'true' ? dispatch(loadUser())} /> : <>} + {user?.attributes['custom:agreed_terms'] !== 'true' && dispatch(loadUser())} />} Date: Fri, 15 Jul 2022 09:00:06 -0300 Subject: [PATCH 21/37] Disable closing modal by clicking outside --- src/components/data-management/PrivacyPolicyIntercept.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index a166127f21..073425f82c 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -42,6 +42,7 @@ const PrivacyPolicyIntercept = (props) => { cancelButtonProps={{ danger: true }} okButtonProps={{ disabled: agreedPrivacyPolicy !== 'true' }} closable={false} + maskClosable={false} onOk={async () => { await Auth.updateUserAttributes( user, From ef10bb5ac8f70f07082c89865c62f818d3e8aa8b Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 10:08:06 -0300 Subject: [PATCH 22/37] Add 424 erros message --- src/pages/_app.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 9ba69a9652..f5ec2dde69 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -111,6 +111,16 @@ const WrappedApp = ({ Component, pageProps }) => { ); } + if (httpError === 424) { + return ( + + ); + } + if (httpError === 401) { return ( Date: Fri, 15 Jul 2022 10:54:50 -0300 Subject: [PATCH 23/37] Add domainName loading from ssr --- .../data-management/SamplesTable.test.jsx | 4 ++-- src/__test__/pages/_error.test.jsx | 10 +++++----- .../[experimentId]/data-management/index.test.jsx | 4 ++-- .../pages/settings/profile/index.test.jsx | 4 ++-- src/__test__/utils/work/fetchWork.test.js | 2 +- src/components/ContentWrapper.jsx | 8 ++++++-- src/redux/actionTypes/networkResources.js | 4 ++-- .../networkResources/loadDeploymentInfo.js | 13 +++++++++++++ .../actions/networkResources/loadEnvironment.js | 12 ------------ src/redux/reducers/index.js | 1 - .../networkResources/deploymentInfoLoaded.js | 11 +++++++++++ src/redux/reducers/networkResources/index.js | 8 ++++---- .../reducers/networkResources/loadEnvironment.js | 9 --------- src/utils/cache.js | 2 +- src/utils/{environment.js => deploymentInfo.js} | 15 ++++++++++----- src/utils/http/handleError.js | 12 ++++++------ src/utils/socketConnection.js | 2 +- src/utils/ssr/getEnvironmentInfo.js | 12 ++++++------ src/utils/tracking.js | 10 +++++----- src/utils/work/fetchWork.js | 2 +- 20 files changed, 78 insertions(+), 67 deletions(-) create mode 100644 src/redux/actions/networkResources/loadDeploymentInfo.js delete mode 100644 src/redux/actions/networkResources/loadEnvironment.js create mode 100644 src/redux/reducers/networkResources/deploymentInfoLoaded.js delete mode 100644 src/redux/reducers/networkResources/loadEnvironment.js rename src/utils/{environment.js => deploymentInfo.js} (60%) diff --git a/src/__test__/components/data-management/SamplesTable.test.jsx b/src/__test__/components/data-management/SamplesTable.test.jsx index bc4fc05c0d..4c3ef1cf9c 100644 --- a/src/__test__/components/data-management/SamplesTable.test.jsx +++ b/src/__test__/components/data-management/SamplesTable.test.jsx @@ -19,7 +19,7 @@ import thunk from 'redux-thunk'; import createTestComponentFactory from '__test__/test-utils/testComponentFactory'; import { loadExperiments, setActiveExperiment } from 'redux/actions/experiments'; -import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; +import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInfo'; import { loadSamples } from 'redux/actions/samples'; import mockDemoExperiments from '__test__/test-utils/mockData/mockDemoExperiments.json'; @@ -109,7 +109,7 @@ describe('Samples table', () => { // Defaults to project with samples await storeState.dispatch(setActiveExperiment(experimentWithSamplesId)); - await storeState.dispatch(loadEnvironment('test')); + await storeState.dispatch(loadDeploymentInfo({ environment: 'test' })); await storeState.dispatch(loadUser()); }); diff --git a/src/__test__/pages/_error.test.jsx b/src/__test__/pages/_error.test.jsx index 8fba07c472..1aa0dd2f37 100644 --- a/src/__test__/pages/_error.test.jsx +++ b/src/__test__/pages/_error.test.jsx @@ -6,7 +6,7 @@ import { Provider } from 'react-redux'; import { makeStore } from 'redux/store'; import postErrorToSlack from 'utils/postErrorToSlack'; -import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; +import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInfo'; import createTestComponentFactory from '__test__/test-utils/testComponentFactory'; @@ -36,7 +36,7 @@ describe('ErrorPage', () => { beforeEach(() => { jest.clearAllMocks(); storeState = makeStore(); - storeState.dispatch(loadEnvironment('production')); + storeState.dispatch(loadDeploymentInfo({ environment: 'production' })); }); it('Renders properly without props', () => { @@ -70,7 +70,7 @@ describe('ErrorPage', () => { }); it('Should post error to Slack if environment is production', () => { - storeState.dispatch(loadEnvironment('production')); + storeState.dispatch(loadDeploymentInfo({ environment: 'production' })); renderErrorPage(mockErrorProp, storeState); @@ -79,7 +79,7 @@ describe('ErrorPage', () => { }); it('Should post error to Slack if environment is staging', () => { - storeState.dispatch(loadEnvironment('staging')); + storeState.dispatch(loadDeploymentInfo({ environment: 'staging' })); renderErrorPage(mockErrorProp, storeState); @@ -88,7 +88,7 @@ describe('ErrorPage', () => { }); it('Should not post error to Slack if environment is not production', () => { - storeState.dispatch(loadEnvironment('development')); + storeState.dispatch(loadDeploymentInfo({ environment: 'development' })); renderErrorPage(mockErrorProp, storeState); diff --git a/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx b/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx index 5b7c5c1776..c861cc691b 100644 --- a/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx +++ b/src/__test__/pages/experiments/[experimentId]/data-management/index.test.jsx @@ -20,7 +20,7 @@ import DataManagementPage from 'pages/data-management'; import userEvent from '@testing-library/user-event'; import { setActiveExperiment } from 'redux/actions/experiments'; -import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; +import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInfo'; import { loadUser } from 'redux/actions/user'; jest.mock('utils/data-management/downloadFromUrl'); @@ -80,7 +80,7 @@ describe('Data Management page', () => { fetchMock.mockIf(/.*/, mockAPI(mockAPIResponse)); storeState = makeStore(); - storeState.dispatch(loadEnvironment('test')); + storeState.dispatch(loadDeploymentInfo({ environment: 'test' })); storeState.dispatch(loadUser()); }); diff --git a/src/__test__/pages/settings/profile/index.test.jsx b/src/__test__/pages/settings/profile/index.test.jsx index 76fc404c29..9e00417160 100644 --- a/src/__test__/pages/settings/profile/index.test.jsx +++ b/src/__test__/pages/settings/profile/index.test.jsx @@ -12,7 +12,7 @@ import pushNotificationMessage from 'utils/pushNotificationMessage'; import { Provider } from 'react-redux'; import { loadUser } from 'redux/actions/user'; -import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; +import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInfo'; jest.mock('next/router', () => ({ useRouter: jest.fn(), @@ -56,7 +56,7 @@ describe('Profile page', () => { setUpAuthMocks(); - store.dispatch(loadEnvironment('test')); + store.dispatch(loadDeploymentInfo({ environment: 'test' })); store.dispatch(loadUser()); }); diff --git a/src/__test__/utils/work/fetchWork.test.js b/src/__test__/utils/work/fetchWork.test.js index 1b4cab5745..63b04e58da 100644 --- a/src/__test__/utils/work/fetchWork.test.js +++ b/src/__test__/utils/work/fetchWork.test.js @@ -1,6 +1,6 @@ /* eslint-disable global-require */ import { fetchWork } from 'utils/work/fetchWork'; -import Environment from 'utils/environment'; +import { Environment } from 'utils/deploymentInfo'; const { mockGeneExpressionData, diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index ff5c1f0791..c7e7b21bf1 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -27,7 +27,7 @@ import PreloadContent from 'components/PreloadContent'; import experimentUpdatesHandler from 'utils/experimentUpdatesHandler'; import { getBackendStatus } from 'redux/selectors'; import { loadBackendStatus } from 'redux/actions/backendStatus'; -import { isBrowser } from 'utils/environment'; +import { DomainName, isBrowser } from 'utils/deploymentInfo'; import Error from 'pages/_error'; @@ -51,6 +51,8 @@ const ContentWrapper = (props) => { const currentExperimentIdRef = useRef(routeExperimentId); const activeExperimentId = useSelector((state) => state?.experiments?.meta?.activeExperimentId); const activeExperiment = useSelector((state) => state.experiments[activeExperimentId]); + + const domainName = useSelector((state) => state.networkResources.domainName); const user = useSelector((state) => state.user.current); const samples = useSelector((state) => state.samples); @@ -329,7 +331,9 @@ const ContentWrapper = (props) => { return ( <> - {user?.attributes['custom:agreed_terms'] !== 'true' && dispatch(loadUser())} />} + {user?.attributes['custom:agreed_terms'] !== 'true' && domainName === DomainName.BIOMAGE && ( + dispatch(loadUser())} /> + )} (dispatch) => { + dispatch({ + type: NETWORK_RESOURCES_DEPLOYMENT_INFO_LOADED, + payload: { + environment, + domainName, + }, + }); +}; + +export default loadDeploymentInfo; diff --git a/src/redux/actions/networkResources/loadEnvironment.js b/src/redux/actions/networkResources/loadEnvironment.js deleted file mode 100644 index 702c70aafd..0000000000 --- a/src/redux/actions/networkResources/loadEnvironment.js +++ /dev/null @@ -1,12 +0,0 @@ -import { NETWORK_RESOURCES_LOAD_ENVIRONMENT } from '../../actionTypes/networkResources'; - -const loadEnvironment = (environment) => (dispatch) => { - dispatch({ - type: NETWORK_RESOURCES_LOAD_ENVIRONMENT, - payload: { - environment, - }, - }); -}; - -export default loadEnvironment; diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js index 63865496a0..550a18dda9 100644 --- a/src/redux/reducers/index.js +++ b/src/redux/reducers/index.js @@ -39,7 +39,6 @@ const rootReducer = (state, action) => { // we need to keep the old state for these parts of the store newState = { samples: state.samples, - projects: state.projects, backendStatus: state.backendStatus, experiments: state.experiments, networkResources: state.networkResources, diff --git a/src/redux/reducers/networkResources/deploymentInfoLoaded.js b/src/redux/reducers/networkResources/deploymentInfoLoaded.js new file mode 100644 index 0000000000..a3a15e20ae --- /dev/null +++ b/src/redux/reducers/networkResources/deploymentInfoLoaded.js @@ -0,0 +1,11 @@ +/* eslint-disable no-param-reassign */ +import produce from 'immer'; + +const deploymentInfoLoaded = produce((draft, action) => { + const { environment, domainName } = action.payload; + + draft.environment = environment; + draft.domainName = domainName; +}); + +export default deploymentInfoLoaded; diff --git a/src/redux/reducers/networkResources/index.js b/src/redux/reducers/networkResources/index.js index ac35169890..5087923248 100644 --- a/src/redux/reducers/networkResources/index.js +++ b/src/redux/reducers/networkResources/index.js @@ -1,16 +1,16 @@ import { HYDRATE } from 'next-redux-wrapper'; import { - NETWORK_RESOURCES_LOAD_ENVIRONMENT, + NETWORK_RESOURCES_DEPLOYMENT_INFO_LOADED, } from '../../actionTypes/networkResources'; import initialState from './initialState'; -import loadEnvironment from './loadEnvironment'; +import deploymentInfoLoaded from './deploymentInfoLoaded'; import environmentHydrate from './environmentHydrate'; const networkResourcesReducer = (state = initialState, action) => { switch (action.type) { - case NETWORK_RESOURCES_LOAD_ENVIRONMENT: { - return loadEnvironment(state, action); + case NETWORK_RESOURCES_DEPLOYMENT_INFO_LOADED: { + return deploymentInfoLoaded(state, action); } case HYDRATE: { diff --git a/src/redux/reducers/networkResources/loadEnvironment.js b/src/redux/reducers/networkResources/loadEnvironment.js deleted file mode 100644 index e64a719964..0000000000 --- a/src/redux/reducers/networkResources/loadEnvironment.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable no-param-reassign */ -import produce from 'immer'; - -const loadEnvironment = produce((draft, action) => { - const { environment } = action.payload; - draft.environment = environment; -}); - -export default loadEnvironment; diff --git a/src/utils/cache.js b/src/utils/cache.js index 4c4e28ae37..35d0748f6d 100644 --- a/src/utils/cache.js +++ b/src/utils/cache.js @@ -2,7 +2,7 @@ /* eslint-disable no-param-reassign */ /* eslint-disable max-classes-per-file */ import * as localForage from 'localforage'; -import { isBrowser } from './environment'; +import { isBrowser } from './deploymentInfo'; const currentCacheVersion = 'biomage_0.0.1'; const previousCacheVersions = ['biomage']; diff --git a/src/utils/environment.js b/src/utils/deploymentInfo.js similarity index 60% rename from src/utils/environment.js rename to src/utils/deploymentInfo.js index 7472035510..79673da540 100644 --- a/src/utils/environment.js +++ b/src/utils/deploymentInfo.js @@ -6,7 +6,11 @@ const Environment = { PRODUCTION: 'production', }; -const ssrGetCurrentEnvironment = () => { +const DomainName = { + BIOMAGE: 'scp.biomage.net', +}; + +const ssrGetDeploymentInfo = () => { let currentEnvironment = null; if (process.env.NODE_ENV === 'test') { @@ -14,7 +18,7 @@ const ssrGetCurrentEnvironment = () => { } if (!process.env) { - throw new Error('ssrGetCurrentEnvironment must be called on the server side. Refer to `store.networkResources.environment` for the actual environment.'); + throw new Error('ssrGetDeploymentInfo must be called on the server side. Refer to `store.networkResources.environment` for the actual environment.'); } switch (process.env.K8S_ENV) { @@ -29,8 +33,9 @@ const ssrGetCurrentEnvironment = () => { break; } - return currentEnvironment; + return { environment: currentEnvironment, domainName: DomainName[process.env.DOMAIN_NAME] }; }; -export { isBrowser, ssrGetCurrentEnvironment }; -export default Environment; +export { + isBrowser, ssrGetDeploymentInfo, DomainName, Environment, +}; diff --git a/src/utils/http/handleError.js b/src/utils/http/handleError.js index 40de6891e1..7e02669c80 100644 --- a/src/utils/http/handleError.js +++ b/src/utils/http/handleError.js @@ -1,11 +1,11 @@ -import Environment, { ssrGetCurrentEnvironment } from 'utils/environment'; +import { Environment, ssrGetDeploymentInfo } from 'utils/deploymentInfo'; import postErrorToSlack from 'utils/postErrorToSlack'; import pushNotificationMessage from 'utils/pushNotificationMessage'; import endUserMessages from 'utils/endUserMessages'; import httpStatusCodes from 'utils/http/httpStatusCodes'; -const env = ssrGetCurrentEnvironment(); +const { environment } = ssrGetDeploymentInfo(); const handleCodedErrors = (error, message, notifyUser) => { let errorMessage = message; @@ -32,11 +32,11 @@ const handleGenericErrors = (error, message, notifyUser) => { pushNotificationMessage('error', `${message}`); } - if (env === Environment.PRODUCTION) { - // add the intended user message to the error to now where - // the error comes from + if (environment === Environment.PRODUCTION) { + // add the intended user message to the error to now where + // the error comes from if (message) { - // eslint-disable-next-line no-param-reassign + // eslint-disable-next-line no-param-reassign error.message += message; } postErrorToSlack(error); diff --git a/src/utils/socketConnection.js b/src/utils/socketConnection.js index d19d03e082..dca3d34007 100644 --- a/src/utils/socketConnection.js +++ b/src/utils/socketConnection.js @@ -1,6 +1,6 @@ import socketIOClient from 'socket.io-client'; import getApiEndpoint from './apiEndpoint'; -import { isBrowser } from './environment'; +import { isBrowser } from './deploymentInfo'; const connectionPromise = new Promise((resolve, reject) => { /** diff --git a/src/utils/ssr/getEnvironmentInfo.js b/src/utils/ssr/getEnvironmentInfo.js index 2fdedfdf25..1b98997358 100644 --- a/src/utils/ssr/getEnvironmentInfo.js +++ b/src/utils/ssr/getEnvironmentInfo.js @@ -1,18 +1,18 @@ -import loadEnvironment from 'redux/actions/networkResources/loadEnvironment'; -import { ssrGetCurrentEnvironment } from 'utils/environment'; +import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInfo'; +import { ssrGetDeploymentInfo } from 'utils/deploymentInfo'; -const getAuthenticationInfo = async (context, store) => { +const getEnvironmentInfo = async (context, store) => { if ( store.getState().networkResources.environment ) { return; } - const env = ssrGetCurrentEnvironment(); + const { environment, domainName } = ssrGetDeploymentInfo(); - store.dispatch(loadEnvironment(env)); + store.dispatch(loadDeploymentInfo({ environment, domainName })); return {}; }; -export default getAuthenticationInfo; +export default getEnvironmentInfo; diff --git a/src/utils/tracking.js b/src/utils/tracking.js index 1001c6a639..79ab939d5e 100644 --- a/src/utils/tracking.js +++ b/src/utils/tracking.js @@ -1,6 +1,6 @@ import { init, push } from '@socialgouv/matomo-next'; import Auth from '@aws-amplify/auth'; -import Env from './environment'; +import { Environment } from './deploymentInfo'; const MATOMO_URL = 'https://biomage.matomo.cloud'; @@ -9,24 +9,24 @@ const MATOMO_URL = 'https://biomage.matomo.cloud'; // To test locally, just enable the development environemnt. // The Site Ids are defined in biomage.matomo.cloud const trackingInfo = { - [Env.PRODUCTION]: { + [Environment.PRODUCTION]: { enabled: true, siteId: 1, containerId: 'lkIodjnO', }, - [Env.STAGING]: { + [Environment.STAGING]: { enabled: false, siteId: 2, containerId: 'FX7UBNS6', }, - [Env.DEVELOPMENT]: { + [Environment.DEVELOPMENT]: { enabled: false, siteId: 3, containerId: 'lS8ZRMXJ', }, }; -let env = Env.DEVELOPMENT; +let env = Environment.DEVELOPMENT; const getTrackingDetails = (e) => trackingInfo[e]; diff --git a/src/utils/work/fetchWork.js b/src/utils/work/fetchWork.js index e123628ecd..2df000d00c 100644 --- a/src/utils/work/fetchWork.js +++ b/src/utils/work/fetchWork.js @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ import { MD5 } from 'object-hash'; -import Environment, { isBrowser } from 'utils/environment'; +import { Environment, isBrowser } from 'utils/deploymentInfo'; import { calculateZScore } from 'utils/postRequestProcessing'; import { getBackendStatus } from 'redux/selectors'; From 5cdffd35ad871cb7aa4de58c2c43fc1bb4219e82 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 10:57:28 -0300 Subject: [PATCH 24/37] Make it include biomage staging --- src/utils/deploymentInfo.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/deploymentInfo.js b/src/utils/deploymentInfo.js index 79673da540..828777d2ad 100644 --- a/src/utils/deploymentInfo.js +++ b/src/utils/deploymentInfo.js @@ -8,6 +8,7 @@ const Environment = { const DomainName = { BIOMAGE: 'scp.biomage.net', + BIOMAGE_STAGING: 'scp-staging.biomage.net', }; const ssrGetDeploymentInfo = () => { @@ -33,7 +34,12 @@ const ssrGetDeploymentInfo = () => { break; } - return { environment: currentEnvironment, domainName: DomainName[process.env.DOMAIN_NAME] }; + let domainName; + if ([DomainName.BIOMAGE, DomainName.BIOMAGE_STAGING].includes(domainName)) { + domainName = DomainName.BIOMAGE; + } + + return { environment: currentEnvironment, domainName }; }; export { From 6eedf1cc07bb5d0b20b0d269615fa67ef4a67f75 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 11:12:29 -0300 Subject: [PATCH 25/37] Minor refactoring --- src/components/ContentWrapper.jsx | 4 ++-- .../data-management/ExampleExperimentsSpace.jsx | 8 ++++++-- src/pages/data-management/index.jsx | 6 ++++-- src/utils/deploymentInfo.js | 9 +++++++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index c7e7b21bf1..2f3b8ecb2f 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -27,7 +27,7 @@ import PreloadContent from 'components/PreloadContent'; import experimentUpdatesHandler from 'utils/experimentUpdatesHandler'; import { getBackendStatus } from 'redux/selectors'; import { loadBackendStatus } from 'redux/actions/backendStatus'; -import { DomainName, isBrowser } from 'utils/deploymentInfo'; +import { DomainName, isBrowser, privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; import Error from 'pages/_error'; @@ -331,7 +331,7 @@ const ContentWrapper = (props) => { return ( <> - {user?.attributes['custom:agreed_terms'] !== 'true' && domainName === DomainName.BIOMAGE && ( + {privacyPolicyIsNotAccepted(user, domainName) && ( dispatch(loadUser())} /> )} diff --git a/src/components/data-management/ExampleExperimentsSpace.jsx b/src/components/data-management/ExampleExperimentsSpace.jsx index 3cf3602d5e..5a1483031b 100644 --- a/src/components/data-management/ExampleExperimentsSpace.jsx +++ b/src/components/data-management/ExampleExperimentsSpace.jsx @@ -7,19 +7,23 @@ import { import { setActiveExperiment, loadExperiments } from 'redux/actions/experiments'; import fetchAPI from 'utils/http/fetchAPI'; +import { privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; const { Paragraph, Text } = Typography; const ExampleExperimentsSpace = ({ introductionText, imageStyle }) => { const dispatch = useDispatch(); - const environment = useSelector((state) => state?.networkResources?.environment); + const { + environment = undefined, domainName = undefined, + } = useSelector((state) => state?.networkResources ?? {}); + const user = useSelector((state) => state?.user?.current); const [exampleExperiments, setExampleExperiments] = useState([]); useEffect(() => { - if (!environment || user?.attributes['custom:agreed_terms'] !== 'true') return; + if (!environment || privacyPolicyIsNotAccepted(user, domainName)) return; fetchAPI('/v2/experiments/examples').then((experiments) => { setExampleExperiments(experiments); diff --git a/src/pages/data-management/index.jsx b/src/pages/data-management/index.jsx index 3714e06720..282425466c 100644 --- a/src/pages/data-management/index.jsx +++ b/src/pages/data-management/index.jsx @@ -15,6 +15,7 @@ import { loadProcessingSettings } from 'redux/actions/experimentSettings'; import loadBackendStatus from 'redux/actions/backendStatus/loadBackendStatus'; import { loadSamples } from 'redux/actions/samples'; import ExampleExperimentsSpace from 'components/data-management/ExampleExperimentsSpace'; +import { DomainName, privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; const DataManagementPage = () => { const dispatch = useDispatch(); @@ -24,6 +25,7 @@ const DataManagementPage = () => { const { activeExperimentId } = useSelector((state) => state.experiments.meta); const experiments = useSelector(((state) => state.experiments)); const user = useSelector((state) => state.user.current); + const domainName = useSelector((state) => state.networkResources?.domainName); const activeExperiment = experiments[activeExperimentId]; const { saving: experimentsSaving } = experiments.meta; @@ -32,7 +34,7 @@ const DataManagementPage = () => { const [newProjectModalVisible, setNewProjectModalVisible] = useState(false); useEffect(() => { - if (user?.attributes['custom:agreed_terms'] !== 'true') return; + if (privacyPolicyIsNotAccepted(user, domainName)) return; if (experiments.ids.length === 0) dispatch(loadExperiments()); }, [user]); @@ -43,7 +45,7 @@ const DataManagementPage = () => { }; useEffect(() => { - if (!activeExperimentId || user?.attributes['custom:agreed_terms'] !== 'true') return; + if (!activeExperimentId || privacyPolicyIsNotAccepted(user, domainName)) return; dispatch(loadProcessingSettings(activeExperimentId)); diff --git a/src/utils/deploymentInfo.js b/src/utils/deploymentInfo.js index 828777d2ad..7a9681aa0f 100644 --- a/src/utils/deploymentInfo.js +++ b/src/utils/deploymentInfo.js @@ -1,5 +1,7 @@ const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; +const privacyPolicyIsNotAccepted = (user, domainName) => user?.attributes['custom:agreed_terms'] !== 'true' && domainName === DomainName.BIOMAGE; + const Environment = { DEVELOPMENT: 'development', STAGING: 'staging', @@ -35,7 +37,10 @@ const ssrGetDeploymentInfo = () => { } let domainName; - if ([DomainName.BIOMAGE, DomainName.BIOMAGE_STAGING].includes(domainName)) { + if ( + [DomainName.BIOMAGE, DomainName.BIOMAGE_STAGING].includes(domainName) + || currentEnvironment === Environment.DEVELOPMENT + ) { domainName = DomainName.BIOMAGE; } @@ -43,5 +48,5 @@ const ssrGetDeploymentInfo = () => { }; export { - isBrowser, ssrGetDeploymentInfo, DomainName, Environment, + isBrowser, ssrGetDeploymentInfo, DomainName, Environment, privacyPolicyIsNotAccepted, }; From 7d15ea18601a43b0d28fe08c8d4422a1f50033cf Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 11:26:02 -0300 Subject: [PATCH 26/37] Fix _error.test.jsx --- src/__test__/pages/__snapshots__/_error.test.jsx.snap | 2 ++ src/__test__/pages/_error.test.jsx | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/__test__/pages/__snapshots__/_error.test.jsx.snap b/src/__test__/pages/__snapshots__/_error.test.jsx.snap index 5a4050c955..dc8a953812 100644 --- a/src/__test__/pages/__snapshots__/_error.test.jsx.snap +++ b/src/__test__/pages/__snapshots__/_error.test.jsx.snap @@ -134,6 +134,7 @@ Array [ }, }, "networkResources": Object { + "domainName": "scp.biomage.net", "environment": "production", }, "samples": Object { @@ -284,6 +285,7 @@ Array [ }, }, "networkResources": Object { + "domainName": "scp.biomage.net", "environment": "staging", }, "samples": Object { diff --git a/src/__test__/pages/_error.test.jsx b/src/__test__/pages/_error.test.jsx index 1aa0dd2f37..8358ea18f4 100644 --- a/src/__test__/pages/_error.test.jsx +++ b/src/__test__/pages/_error.test.jsx @@ -7,6 +7,7 @@ import { makeStore } from 'redux/store'; import postErrorToSlack from 'utils/postErrorToSlack'; import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInfo'; +import { DomainName } from 'utils/deploymentInfo'; import createTestComponentFactory from '__test__/test-utils/testComponentFactory'; @@ -36,7 +37,7 @@ describe('ErrorPage', () => { beforeEach(() => { jest.clearAllMocks(); storeState = makeStore(); - storeState.dispatch(loadDeploymentInfo({ environment: 'production' })); + storeState.dispatch(loadDeploymentInfo({ environment: 'production', domainName: DomainName.BIOMAGE })); }); it('Renders properly without props', () => { @@ -70,7 +71,7 @@ describe('ErrorPage', () => { }); it('Should post error to Slack if environment is production', () => { - storeState.dispatch(loadDeploymentInfo({ environment: 'production' })); + storeState.dispatch(loadDeploymentInfo({ environment: 'production', domainName: DomainName.BIOMAGE })); renderErrorPage(mockErrorProp, storeState); @@ -79,7 +80,7 @@ describe('ErrorPage', () => { }); it('Should post error to Slack if environment is staging', () => { - storeState.dispatch(loadDeploymentInfo({ environment: 'staging' })); + storeState.dispatch(loadDeploymentInfo({ environment: 'staging', domainName: DomainName.BIOMAGE })); renderErrorPage(mockErrorProp, storeState); @@ -88,7 +89,7 @@ describe('ErrorPage', () => { }); it('Should not post error to Slack if environment is not production', () => { - storeState.dispatch(loadDeploymentInfo({ environment: 'development' })); + storeState.dispatch(loadDeploymentInfo({ environment: 'development', domainName: DomainName.BIOMAGE })); renderErrorPage(mockErrorProp, storeState); From 9ae59ce288c93fbb24c714210d43b6a2d360c612 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 11:31:09 -0300 Subject: [PATCH 27/37] Fix --- src/utils/deploymentInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/deploymentInfo.js b/src/utils/deploymentInfo.js index 7a9681aa0f..8a8c263129 100644 --- a/src/utils/deploymentInfo.js +++ b/src/utils/deploymentInfo.js @@ -38,7 +38,7 @@ const ssrGetDeploymentInfo = () => { let domainName; if ( - [DomainName.BIOMAGE, DomainName.BIOMAGE_STAGING].includes(domainName) + [DomainName.BIOMAGE, DomainName.BIOMAGE_STAGING].includes(process.env.DOMAIN_NAME) || currentEnvironment === Environment.DEVELOPMENT ) { domainName = DomainName.BIOMAGE; From f62cc5477ed148812a363e2bd6bbcb9d98da9db4 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 11:33:50 -0300 Subject: [PATCH 28/37] Small fixes --- src/components/ContentWrapper.jsx | 2 +- src/pages/_app.jsx | 2 +- src/utils/http/handleError.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ContentWrapper.jsx b/src/components/ContentWrapper.jsx index 2f3b8ecb2f..76dd869997 100644 --- a/src/components/ContentWrapper.jsx +++ b/src/components/ContentWrapper.jsx @@ -27,7 +27,7 @@ import PreloadContent from 'components/PreloadContent'; import experimentUpdatesHandler from 'utils/experimentUpdatesHandler'; import { getBackendStatus } from 'redux/selectors'; import { loadBackendStatus } from 'redux/actions/backendStatus'; -import { DomainName, isBrowser, privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; +import { isBrowser, privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; import Error from 'pages/_error'; diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index f5ec2dde69..6f331a1c74 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -116,7 +116,7 @@ const WrappedApp = ({ Component, pageProps }) => { ); } diff --git a/src/utils/http/handleError.js b/src/utils/http/handleError.js index 7e02669c80..47df2903f9 100644 --- a/src/utils/http/handleError.js +++ b/src/utils/http/handleError.js @@ -33,7 +33,7 @@ const handleGenericErrors = (error, message, notifyUser) => { } if (environment === Environment.PRODUCTION) { - // add the intended user message to the error to now where + // add the intended user message to the error to know where // the error comes from if (message) { // eslint-disable-next-line no-param-reassign From fc7b9d091e390fed4c2cd5656110e68b85230b51 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 11:34:19 -0300 Subject: [PATCH 29/37] Remove unused import --- src/pages/data-management/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/data-management/index.jsx b/src/pages/data-management/index.jsx index 282425466c..99134273b9 100644 --- a/src/pages/data-management/index.jsx +++ b/src/pages/data-management/index.jsx @@ -15,7 +15,7 @@ import { loadProcessingSettings } from 'redux/actions/experimentSettings'; import loadBackendStatus from 'redux/actions/backendStatus/loadBackendStatus'; import { loadSamples } from 'redux/actions/samples'; import ExampleExperimentsSpace from 'components/data-management/ExampleExperimentsSpace'; -import { DomainName, privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; +import { privacyPolicyIsNotAccepted } from 'utils/deploymentInfo'; const DataManagementPage = () => { const dispatch = useDispatch(); From da9f7f9ea9b762b62bb406799c6a1a96f729e410 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 13:30:00 -0300 Subject: [PATCH 30/37] Update text --- src/pages/404.jsx | 8 ++++++-- src/pages/_app.jsx | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pages/404.jsx b/src/pages/404.jsx index 78026e6120..d97f563544 100644 --- a/src/pages/404.jsx +++ b/src/pages/404.jsx @@ -5,7 +5,9 @@ import FeedbackButton from 'components/header/FeedbackButton'; const { Title } = Typography; -const NotFoundPage = ({ title, subTitle, hint }) => ( +const NotFoundPage = ({ + title, subTitle, hint, primaryActionText, +}) => ( {title}} icon={( @@ -38,7 +40,7 @@ const NotFoundPage = ({ title, subTitle, hint }) => ( extra={( <> @@ -50,12 +52,14 @@ NotFoundPage.defaultProps = { hint: '', title: 'Page not found', subTitle: 'We can\'t seem to find the page you\'re looking for.', + primaryActionText: 'Go home', }; NotFoundPage.propTypes = { title: PropTypes.string, subTitle: PropTypes.string, hint: PropTypes.string, + primaryActionText: PropTypes.string, }; export default NotFoundPage; diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 6f331a1c74..ba6b013e6e 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -115,8 +115,9 @@ const WrappedApp = ({ Component, pageProps }) => { return ( ); } From 6b20c08ed3e7008c143ce1e761293424040149d7 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 13:58:34 -0300 Subject: [PATCH 31/37] Fix networkResources hydration --- src/redux/reducers/networkResources/environmentHydrate.js | 3 ++- src/redux/reducers/networkResources/initialState.js | 1 + src/utils/ssr/getEnvironmentInfo.js | 7 ++----- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/redux/reducers/networkResources/environmentHydrate.js b/src/redux/reducers/networkResources/environmentHydrate.js index a3f5a0788d..76f676ac0b 100644 --- a/src/redux/reducers/networkResources/environmentHydrate.js +++ b/src/redux/reducers/networkResources/environmentHydrate.js @@ -3,8 +3,9 @@ import produce from 'immer'; import initialState from './initialState'; const environmentHydrate = produce((draft, action) => { - const { environment } = action.payload.networkResources; + const { environment, domainName } = action.payload.networkResources; draft.environment = environment; + draft.domainName = domainName; }, initialState); export default environmentHydrate; diff --git a/src/redux/reducers/networkResources/initialState.js b/src/redux/reducers/networkResources/initialState.js index 7ad5eca813..749905099b 100644 --- a/src/redux/reducers/networkResources/initialState.js +++ b/src/redux/reducers/networkResources/initialState.js @@ -1,5 +1,6 @@ const initialState = { environment: undefined, + domainName: undefined, }; export default initialState; diff --git a/src/utils/ssr/getEnvironmentInfo.js b/src/utils/ssr/getEnvironmentInfo.js index 1b98997358..2cc0c9b049 100644 --- a/src/utils/ssr/getEnvironmentInfo.js +++ b/src/utils/ssr/getEnvironmentInfo.js @@ -2,11 +2,8 @@ import loadDeploymentInfo from 'redux/actions/networkResources/loadDeploymentInf import { ssrGetDeploymentInfo } from 'utils/deploymentInfo'; const getEnvironmentInfo = async (context, store) => { - if ( - store.getState().networkResources.environment - ) { - return; - } + const { networkResources } = store.getState(); + if (networkResources.environment && networkResources.domainName) return; const { environment, domainName } = ssrGetDeploymentInfo(); From 7cab480b24519ee62e31615c84cbd498f3d67996 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Fri, 15 Jul 2022 14:03:21 -0300 Subject: [PATCH 32/37] Update snapshots --- .../experiments/__snapshots__/switchExperiment.test.js.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap b/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap index be72f2c042..e6ef334ab4 100644 --- a/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap +++ b/src/__test__/redux/actions/experiments/__snapshots__/switchExperiment.test.js.snap @@ -183,6 +183,7 @@ Object { }, }, "networkResources": Object { + "domainName": undefined, "environment": undefined, }, "samples": Object { @@ -510,6 +511,7 @@ Object { }, }, "networkResources": Object { + "domainName": undefined, "environment": undefined, }, "samples": Object { From 483b740890e2f01450f841230b20f1a8e5263ae3 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Mon, 18 Jul 2022 10:16:30 -0300 Subject: [PATCH 33/37] Add privacyPolicyIsNotAccepted tsts --- src/__test__/utils/deploymentInfo.test.js | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/__test__/utils/deploymentInfo.test.js diff --git a/src/__test__/utils/deploymentInfo.test.js b/src/__test__/utils/deploymentInfo.test.js new file mode 100644 index 0000000000..7d15a8176c --- /dev/null +++ b/src/__test__/utils/deploymentInfo.test.js @@ -0,0 +1,28 @@ +import { + DomainName, privacyPolicyIsNotAccepted, +} from 'utils/deploymentInfo'; + +describe('deploymentInfo', () => { + describe('privacyPolicyIsNotAccepted', () => { + it('Returns false for users that accepted privacy policy', () => { + const user = { attributes: { 'custom:agreed_terms': 'true' } }; + const domainName = DomainName.BIOMAGE; + + expect(privacyPolicyIsNotAccepted(user, domainName)).toEqual(false); + }); + + it('Returns false for users that arent in Biomage deployment', () => { + const user = { attributes: {} }; + const domainName = 'Someotherdomain.com'; + + expect(privacyPolicyIsNotAccepted(user, domainName)).toEqual(false); + }); + + it('Returns true for users that still need to accept terms in Biomage', () => { + const user = { attributes: {} }; + const domainName = DomainName.BIOMAGE; + + expect(privacyPolicyIsNotAccepted(user, domainName)).toEqual(true); + }); + }); +}); From 3b257e3dd01eb575f707bd9a35f81578a6c313b9 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Mon, 18 Jul 2022 10:49:58 -0300 Subject: [PATCH 34/37] Some minor refctoring and add tests fro ssrGetDeploymentINfo --- src/__test__/utils/deploymentInfo.test.js | 74 ++++++++++++++++++++++- src/utils/deploymentInfo.js | 23 ++++--- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/__test__/utils/deploymentInfo.test.js b/src/__test__/utils/deploymentInfo.test.js index 7d15a8176c..e1b4181478 100644 --- a/src/__test__/utils/deploymentInfo.test.js +++ b/src/__test__/utils/deploymentInfo.test.js @@ -1,5 +1,5 @@ import { - DomainName, privacyPolicyIsNotAccepted, + ssrGetDeploymentInfo, DomainName, privacyPolicyIsNotAccepted, Environment, } from 'utils/deploymentInfo'; describe('deploymentInfo', () => { @@ -24,5 +24,77 @@ describe('deploymentInfo', () => { expect(privacyPolicyIsNotAccepted(user, domainName)).toEqual(true); }); + + it('Returns true for users that still need to accept terms in Biomage staging', () => { + const user = { attributes: {} }; + const domainName = DomainName.BIOMAGE_STAGING; + + expect(privacyPolicyIsNotAccepted(user, domainName)).toEqual(true); + }); + }); + + describe('ssrGetDeploymentInfo', () => { + let originalEnv; + + // We are going to mess with the process env so save the original to avoid leak into other tests + beforeAll(() => { + originalEnv = { ...process.env }; + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it('Throws if not called in server side', () => { + process.env = undefined; + + expect(ssrGetDeploymentInfo).toThrowError( + 'ssrGetDeploymentInfo must be called on the server side. Refer to `store.networkResources.environment` for the actual environment.', + ); + }); + + it('Works with test node env', () => { + process.env = { NODE_ENV: 'test' }; + + expect(ssrGetDeploymentInfo()).toEqual({ + environment: Environment.DEVELOPMENT, + domainName: DomainName.BIOMAGE, + }); + }); + + it('Works with prod k8s env in biomage domain', () => { + process.env = { + NODE_ENV: Environment.PRODUCTION, + K8S_ENV: Environment.PRODUCTION, + DOMAIN_NAME: DomainName.BIOMAGE, + }; + + expect(ssrGetDeploymentInfo()).toEqual({ + environment: Environment.PRODUCTION, + domainName: DomainName.BIOMAGE, + }); + }); + + it('Works with staging k8s env in biomage staging domain', () => { + process.env = { + NODE_ENV: Environment.PRODUCTION, + K8S_ENV: Environment.STAGING, + DOMAIN_NAME: DomainName.BIOMAGE_STAGING, + }; + + expect(ssrGetDeploymentInfo()).toEqual({ + environment: Environment.STAGING, + domainName: DomainName.BIOMAGE_STAGING, + }); + }); + + it('Works in development', () => { + process.env = { NODE_ENV: Environment.DEVELOPMENT }; + + expect(ssrGetDeploymentInfo()).toEqual({ + environment: Environment.DEVELOPMENT, + domainName: DomainName.BIOMAGE, + }); + }); }); }); diff --git a/src/utils/deploymentInfo.js b/src/utils/deploymentInfo.js index 8a8c263129..96761e20bc 100644 --- a/src/utils/deploymentInfo.js +++ b/src/utils/deploymentInfo.js @@ -1,6 +1,9 @@ const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; -const privacyPolicyIsNotAccepted = (user, domainName) => user?.attributes['custom:agreed_terms'] !== 'true' && domainName === DomainName.BIOMAGE; +const privacyPolicyIsNotAccepted = (user, domainName) => ( + user?.attributes['custom:agreed_terms'] !== 'true' + && (domainName === DomainName.BIOMAGE || domainName === DomainName.BIOMAGE_STAGING) +); const Environment = { DEVELOPMENT: 'development', @@ -16,14 +19,14 @@ const DomainName = { const ssrGetDeploymentInfo = () => { let currentEnvironment = null; - if (process.env.NODE_ENV === 'test') { - return Environment.DEVELOPMENT; - } - if (!process.env) { throw new Error('ssrGetDeploymentInfo must be called on the server side. Refer to `store.networkResources.environment` for the actual environment.'); } + if (process.env.NODE_ENV === 'test') { + return { environment: Environment.DEVELOPMENT, domainName: DomainName.BIOMAGE }; + } + switch (process.env.K8S_ENV) { case 'production': currentEnvironment = Environment.PRODUCTION; @@ -36,13 +39,9 @@ const ssrGetDeploymentInfo = () => { break; } - let domainName; - if ( - [DomainName.BIOMAGE, DomainName.BIOMAGE_STAGING].includes(process.env.DOMAIN_NAME) - || currentEnvironment === Environment.DEVELOPMENT - ) { - domainName = DomainName.BIOMAGE; - } + const domainName = currentEnvironment !== Environment.DEVELOPMENT + ? process.env.DOMAIN_NAME + : DomainName.BIOMAGE; return { environment: currentEnvironment, domainName }; }; From 293abfd5dd76da1ffe06627dd95d9d2cfeb772ac Mon Sep 17 00:00:00 2001 From: cosa65 Date: Mon, 18 Jul 2022 11:45:10 -0300 Subject: [PATCH 35/37] Rever tmp changes, point back to real user pool --- src/utils/ssr/getAuthenticationInfo.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/utils/ssr/getAuthenticationInfo.js b/src/utils/ssr/getAuthenticationInfo.js index b383afadb1..e081ccf42f 100644 --- a/src/utils/ssr/getAuthenticationInfo.js +++ b/src/utils/ssr/getAuthenticationInfo.js @@ -62,22 +62,21 @@ const getAuthenticationInfo = async () => { * because we do not have a way to reliably replicate Cognito in * local development. */ - // const k8sEnv = process.env.K8S_ENV || 'staging'; - // const userPoolName = `test-biomage-userpool-${k8sEnv}`; + const k8sEnv = process.env.K8S_ENV || 'staging'; + const userPoolName = `biomage-user-pool-case-insensitive-${k8sEnv}`; const identityPoolId = IdentityPools.find( - (pool) => pool.IdentityPoolName.includes('test-file-upload-identity-pool-staging-default'), - // (pool) => pool.IdentityPoolName.includes(`${k8sEnv}-${sandboxId}`), + (pool) => pool.IdentityPoolName.includes(`${k8sEnv}-${sandboxId}`), ).IdentityPoolId; - const userPoolId = UserPools.find((pool) => pool.Name === 'test-biomage-user-pool-staging').Id; + const userPoolId = UserPools.find((pool) => pool.Name === userPoolName).Id; const { UserPoolClients } = await userPoolClient.send( new ListUserPoolClientsCommand({ UserPoolId: userPoolId, MaxResults: 60 }), ); const userPoolClientId = UserPoolClients.find((client) => client.ClientName.includes( - `test-biomage-cellscope-cluster-${sandboxId}`, + `cluster-${sandboxId}`, )).ClientId; const [{ UserPoolClient: userPoolClientDetails }, { UserPool: { Domain } }] = await Promise.all([ From 47b2d2e8483b466ddffb02345d7534f64dfeb881 Mon Sep 17 00:00:00 2001 From: cosa65 Date: Tue, 19 Jul 2022 09:09:52 -0300 Subject: [PATCH 36/37] Update privacy policy --- src/components/data-management/PrivacyPolicyIntercept.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/data-management/PrivacyPolicyIntercept.jsx b/src/components/data-management/PrivacyPolicyIntercept.jsx index 073425f82c..b1c0116434 100644 --- a/src/components/data-management/PrivacyPolicyIntercept.jsx +++ b/src/components/data-management/PrivacyPolicyIntercept.jsx @@ -30,7 +30,7 @@ const PrivacyPolicyIntercept = (props) => { const [agreedPrivacyPolicy, setAgreedPrivacyPolicy] = useState(originalAgreedPrivacyPolicy); const [agreedEmails, setAgreedEmails] = useState(originalAgreedEmails ?? 'false'); - const privacyPolicyUrl = 'https://static1.squarespace.com/static/5f355513fc75aa471d47455c/t/61f12e7b7266045b4cb137bc/1643196027265/Biomage_Privacy_Policy_Jan2022.pdf'; + const privacyPolicyUrl = 'https://static1.squarespace.com/static/5f355513fc75aa471d47455c/t/62d67b8cbd2d7f3177d91f83/1658223501108/Privacy+Policy_July+2022.pdf'; return ( Date: Tue, 19 Jul 2022 11:49:13 -0300 Subject: [PATCH 37/37] Fix experiment switch carryover of user --- src/redux/reducers/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/redux/reducers/index.js b/src/redux/reducers/index.js index 550a18dda9..5833023222 100644 --- a/src/redux/reducers/index.js +++ b/src/redux/reducers/index.js @@ -42,9 +42,10 @@ const rootReducer = (state, action) => { backendStatus: state.backendStatus, experiments: state.experiments, networkResources: state.networkResources, - userReducer: state.user, + user: state.user, }; } + return appReducers(newState, action); };