From: Eric Biagiotti Date: Mon, 13 May 2019 20:34:30 +0000 (-0400) Subject: 15088: Handles browser navigation during link account ops X-Git-Tag: 1.4.0~1^2~16 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/e954cfb45dbe418c151144cc42847b848c9b0ebf 15088: Handles browser navigation during link account ops - Inactive page link account button brings the user to the initial link account page Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti --- diff --git a/src/index.tsx b/src/index.tsx index 9f9b27ca..014627a9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -64,6 +64,9 @@ import { loadFileViewersConfig } from '~/store/file-viewers/file-viewers-actions import { collectionAdminActionSet } from '~/views-components/context-menu/action-sets/collection-admin-action-set'; import { processResourceAdminActionSet } from '~/views-components/context-menu/action-sets/process-resource-admin-action-set'; import { projectAdminActionSet } from '~/views-components/context-menu/action-sets/project-admin-action-set'; +import { ACCOUNT_LINK_STATUS_KEY } from '~/services/link-account-service/link-account-service'; +import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions'; +import { matchTokenRoute } from '~/routes/routes'; console.log(`Starting arvados [${getBuildInfo()}]`); @@ -108,6 +111,13 @@ fetchConfig() }); const store = configureStore(history, services); + // Cancel any link account ops in progess unless the user has + // just logged in or there has been a successful link operation + const data = sessionStorage.getItem(ACCOUNT_LINK_STATUS_KEY); + if (!matchTokenRoute(history.location.pathname) && data === null) { + store.dispatch(cancelLinking()); + } + store.subscribe(initListener(history, store, services, config)); store.dispatch(initAuth(config)); store.dispatch(setBuildInfo()); diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 02835fcc..ba7e2a45 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -122,6 +122,9 @@ export const matchLinkAccountRoute = (route: string) => export const matchKeepServicesRoute = (route: string) => matchPath(route, { path: Routes.KEEP_SERVICES }); +export const matchTokenRoute = (route: string) => + matchPath(route, { path: Routes.TOKEN }); + export const matchUsersRoute = (route: string) => matchPath(route, { path: Routes.USERS }); diff --git a/src/services/link-account-service/link-account-service.ts b/src/services/link-account-service/link-account-service.ts index ebae69ac..42fae365 100644 --- a/src/services/link-account-service/link-account-service.ts +++ b/src/services/link-account-service/link-account-service.ts @@ -16,15 +16,15 @@ export class LinkAccountService { protected serverApi: AxiosInstance, protected actions: ApiActions) { } - public saveToSession(account: AccountToLink) { + public saveAccountToLink(account: AccountToLink) { sessionStorage.setItem(USER_LINK_ACCOUNT_KEY, JSON.stringify(account)); } - public removeFromSession() { + public removeAccountToLink() { sessionStorage.removeItem(USER_LINK_ACCOUNT_KEY); } - public getFromSession() { + public getAccountToLink() { const data = sessionStorage.getItem(USER_LINK_ACCOUNT_KEY); return data ? JSON.parse(data) as AccountToLink : undefined; } diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index 1198ff8d..8be02ed0 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -80,7 +80,7 @@ export const login = (uuidPrefix: string, homeCluster: string) => (dispatch: Dis export const logout = (deleteLinkData: boolean = false) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { if (deleteLinkData) { - services.linkAccountService.removeFromSession(); + services.linkAccountService.removeAccountToLink(); } services.authService.removeApiToken(); services.authService.removeUser(); diff --git a/src/store/link-account-panel/link-account-panel-actions.ts b/src/store/link-account-panel/link-account-panel-actions.ts index 61dc657e..55528824 100644 --- a/src/store/link-account-panel/link-account-panel-actions.ts +++ b/src/store/link-account-panel/link-account-panel-actions.ts @@ -14,6 +14,8 @@ import { UserResource } from "~/models/user"; import { GroupResource } from "~/models/group"; import { LinkAccountPanelError, OriginatingUser } from "./link-account-panel-reducer"; import { login, logout } from "~/store/auth/auth-action"; +import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions"; +import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions'; export const linkAccountPanelActions = unionize({ LINK_INIT: ofType<{ targetUser: UserResource | undefined }>(), @@ -69,13 +71,6 @@ export const checkForLinkStatus = () => } }; -export const finishLinking = (status: LinkAccountStatus) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - services.linkAccountService.removeFromSession(); - services.linkAccountService.saveLinkOpStatus(status); - location.reload(); - }; - export const switchUser = (user: UserResource, token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { dispatch(saveUser(user)); @@ -97,12 +92,11 @@ export const linkFailed = () => } dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Account link failed.', kind: SnackbarKind.ERROR , hideDuration: 3000 })); } - dispatch(finishLinking(LinkAccountStatus.FAILED)); + services.linkAccountService.removeAccountToLink(); }; export const loadLinkAccountPanel = () => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - // First check if an account link operation has completed dispatch(checkForLinkStatus()); @@ -112,20 +106,11 @@ export const loadLinkAccountPanel = () => const curToken = getState().auth.apiToken; if (curUser && curToken) { const curUserResource = await services.userService.get(curUser.uuid); - const linkAccountData = services.linkAccountService.getFromSession(); + const linkAccountData = services.linkAccountService.getAccountToLink(); // If there is link account session data, then the user has logged in a second time if (linkAccountData) { - // If the window is refreshed after the second login, cancel the linking - if (window.performance) { - if (performance.navigation.type === PerformanceNavigation.TYPE_BACK_FORWARD || - performance.navigation.type === PerformanceNavigation.TYPE_RELOAD) { - dispatch(cancelLinking()); - return; - } - } - // Use the token of the user we are getting data for. This avoids any admin/non-admin permissions // issues since a user will always be able to query the api server for their own user data. dispatch(saveApiToken(linkAccountData.token)); @@ -155,7 +140,8 @@ export const loadLinkAccountPanel = () => // This should never really happen, but just in case, switch to the user that // originated the linking operation (i.e. the user saved in session data) dispatch(switchUser(savedUserResource, linkAccountData.token)); - dispatch(finishLinking(LinkAccountStatus.FAILED)); + services.linkAccountService.removeAccountToLink(); + dispatch(linkAccountPanelActions.LINK_INIT({targetUser:savedUserResource})); } dispatch(switchUser(params.targetUser, params.targetUserToken)); @@ -183,7 +169,7 @@ export const loadLinkAccountPanel = () => export const startLinking = (t: LinkAccountType) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink; - services.linkAccountService.saveToSession(accountToLink); + services.linkAccountService.saveAccountToLink(accountToLink); const auth = getState().auth; dispatch(logout()); dispatch(login(auth.localCluster, auth.remoteHosts[auth.homeCluster])); @@ -191,15 +177,16 @@ export const startLinking = (t: LinkAccountType) => export const getAccountLinkData = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - return services.linkAccountService.getFromSession(); + return services.linkAccountService.getAccountToLink(); }; export const cancelLinking = () => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { let user: UserResource | undefined; try { + dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN)); // When linking is cancelled switch to the originating user (i.e. the user saved in session data) - const linkAccountData = services.linkAccountService.getFromSession(); + const linkAccountData = services.linkAccountService.getAccountToLink(); if (linkAccountData) { dispatch(saveApiToken(linkAccountData.token)); user = await services.userService.get(linkAccountData.userUuid); @@ -207,7 +194,9 @@ export const cancelLinking = () => } } finally { - dispatch(finishLinking(LinkAccountStatus.CANCELLED)); + services.linkAccountService.removeAccountToLink(); + dispatch(linkAccountPanelActions.LINK_INIT({targetUser:user})); + dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)); } }; @@ -236,6 +225,9 @@ export const linkAccount = () => dispatch(saveApiToken(linkState.userToLinkToken)); await services.linkAccountService.linkAccounts(linkState.targetUserToken, newGroup.uuid); dispatch(switchUser(linkState.targetUser, linkState.targetUserToken)); + services.linkAccountService.removeAccountToLink(); + services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.SUCCESS); + location.reload(); } catch(e) { // If the link operation fails, delete the previously made project @@ -248,8 +240,5 @@ export const linkAccount = () => } throw e; } - finally { - dispatch(finishLinking(LinkAccountStatus.SUCCESS)); - } } }; \ No newline at end of file diff --git a/src/store/link-account-panel/link-account-panel-reducer.ts b/src/store/link-account-panel/link-account-panel-reducer.ts index 80878c34..0e61abdd 100644 --- a/src/store/link-account-panel/link-account-panel-reducer.ts +++ b/src/store/link-account-panel/link-account-panel-reducer.ts @@ -6,6 +6,7 @@ import { linkAccountPanelActions, LinkAccountPanelAction } from "~/store/link-ac import { UserResource } from "~/models/user"; export enum LinkAccountPanelStatus { + NONE, INITIAL, HAS_SESSION_DATA, LINKING, @@ -41,7 +42,7 @@ const initialState = { targetUserToken: undefined, userToLink: undefined, userToLinkToken: undefined, - status: LinkAccountPanelStatus.INITIAL, + status: LinkAccountPanelStatus.NONE, error: LinkAccountPanelError.NONE }; diff --git a/src/views/inactive-panel/inactive-panel.tsx b/src/views/inactive-panel/inactive-panel.tsx index 5f045f69..8d53a21e 100644 --- a/src/views/inactive-panel/inactive-panel.tsx +++ b/src/views/inactive-panel/inactive-panel.tsx @@ -5,11 +5,10 @@ import * as React from 'react'; import { Dispatch } from 'redux'; import { connect } from 'react-redux'; -import { startLinking } from '~/store/link-account-panel/link-account-panel-actions'; import { Grid, Typography, Button } from '@material-ui/core'; import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles'; import { ArvadosTheme } from '~/common/custom-theme'; -import { LinkAccountType } from '~/models/link-account'; +import { navigateToLinkAccount } from '~/store/navigation/navigation-action'; type CssRules = 'root' | 'ontop' | 'title'; @@ -43,7 +42,9 @@ export interface InactivePanelActionProps { } const mapDispatchToProps = (dispatch: Dispatch): InactivePanelActionProps => ({ - startLinking: () => dispatch(startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)) + startLinking: () => { + dispatch(navigateToLinkAccount); + } }); type InactivePanelProps = WithStyles & InactivePanelActionProps; diff --git a/src/views/link-account-panel/link-account-panel-root.tsx b/src/views/link-account-panel/link-account-panel-root.tsx index 2439cc92..19c4b97a 100644 --- a/src/views/link-account-panel/link-account-panel-root.tsx +++ b/src/views/link-account-panel/link-account-panel-root.tsx @@ -67,7 +67,7 @@ export const LinkAccountPanelRoot = withStyles(styles) ( - diff --git a/src/views/main-panel/main-panel-root.tsx b/src/views/main-panel/main-panel-root.tsx index 51287fa8..43bc7fbc 100644 --- a/src/views/main-panel/main-panel-root.tsx +++ b/src/views/main-panel/main-panel-root.tsx @@ -30,23 +30,24 @@ export interface MainPanelRootDataProps { buildInfo: string; uuidPrefix: string; isNotLinking: boolean; + isLinkingPath: boolean; } type MainPanelRootProps = MainPanelRootDataProps & WithStyles; export const MainPanelRoot = withStyles(styles)( - ({ classes, loading, working, user, buildInfo, uuidPrefix, isNotLinking }: MainPanelRootProps) => + ({ classes, loading, working, user, buildInfo, uuidPrefix, isNotLinking, isLinkingPath }: MainPanelRootProps) => loading ? - : <> { isNotLinking ? <> - + { isNotLinking && {working ? : null} - + } - { user ? (user.isActive ? : ) : } - - : user ? : } + { user ? (user.isActive || (!user.isActive && isLinkingPath) ? : ) : } + + ); diff --git a/src/views/main-panel/main-panel.tsx b/src/views/main-panel/main-panel.tsx index 178db25c..5bf03da3 100644 --- a/src/views/main-panel/main-panel.tsx +++ b/src/views/main-panel/main-panel.tsx @@ -8,6 +8,7 @@ import { MainPanelRoot, MainPanelRootDataProps } from '~/views/main-panel/main-p import { isSystemWorking } from '~/store/progress-indicator/progress-indicator-reducer'; import { isWorkbenchLoading } from '~/store/workbench/workbench-actions'; import { LinkAccountPanelStatus } from '~/store/link-account-panel/link-account-panel-reducer'; +import { matchLinkAccountRoute } from '~/routes/routes'; const mapStateToProps = (state: RootState): MainPanelRootDataProps => { return { @@ -16,7 +17,8 @@ const mapStateToProps = (state: RootState): MainPanelRootDataProps => { loading: isWorkbenchLoading(state), buildInfo: state.appInfo.buildInfo, uuidPrefix: state.auth.localCluster, - isNotLinking: state.linkAccountPanel.status === LinkAccountPanelStatus.INITIAL + isNotLinking: state.linkAccountPanel.status === LinkAccountPanelStatus.NONE || state.linkAccountPanel.status === LinkAccountPanelStatus.INITIAL, + isLinkingPath: state.router.location ? matchLinkAccountRoute(state.router.location.pathname) !== null : false }; }; diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 07b05655..e852150c 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; +import { connect } from 'react-redux'; import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles'; import { Route, Switch } from "react-router"; import { ProjectPanel } from "~/views/project-panel/project-panel"; @@ -124,7 +125,12 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ } }); -type WorkbenchPanelProps = WithStyles; +interface WorkbenchDataProps { + isUserActive: boolean; + isNotLinking: boolean; +} + +type WorkbenchPanelProps = WithStyles & WorkbenchDataProps; const defaultSplitterSize = 90; @@ -136,21 +142,21 @@ const getSplitterInitialSize = () => { const saveSplitterSize = (size: number) => localStorage.setItem('splitterSize', size.toString()); export const WorkbenchPanel = - withStyles(styles)(({ classes }: WorkbenchPanelProps) => - - - + + + - + { props.isUserActive && props.isNotLinking && - - + } + - + { props.isNotLinking && } - +