- Inactive page link account button brings the user to the initial link account page
Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <ebiagiotti@veritasgenetics.com>
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()}]`);
});
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<any>(cancelLinking());
+ }
+
store.subscribe(initListener(history, store, services, config));
store.dispatch(initAuth(config));
store.dispatch(setBuildInfo());
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 });
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;
}
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();
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 }>(),
}
};
-export const finishLinking = (status: LinkAccountStatus) =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- services.linkAccountService.removeFromSession();
- services.linkAccountService.saveLinkOpStatus(status);
- location.reload();
- };
-
export const switchUser = (user: UserResource, token: string) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
dispatch(saveUser(user));
}
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<any>, getState: () => RootState, services: ServiceRepository) => {
-
// First check if an account link operation has completed
dispatch(checkForLinkStatus());
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));
// 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));
export const startLinking = (t: LinkAccountType) =>
(dispatch: Dispatch<any>, 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]));
export const getAccountLinkData = () =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- return services.linkAccountService.getFromSession();
+ return services.linkAccountService.getAccountToLink();
};
export const cancelLinking = () =>
async (dispatch: Dispatch<any>, 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);
}
}
finally {
- dispatch(finishLinking(LinkAccountStatus.CANCELLED));
+ services.linkAccountService.removeAccountToLink();
+ dispatch(linkAccountPanelActions.LINK_INIT({targetUser:user}));
+ dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
}
};
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
}
throw e;
}
- finally {
- dispatch(finishLinking(LinkAccountStatus.SUCCESS));
- }
}
};
\ No newline at end of file
import { UserResource } from "~/models/user";
export enum LinkAccountPanelStatus {
+ NONE,
INITIAL,
HAS_SESSION_DATA,
LINKING,
targetUserToken: undefined,
userToLink: undefined,
userToLinkToken: undefined,
- status: LinkAccountPanelStatus.INITIAL,
+ status: LinkAccountPanelStatus.NONE,
error: LinkAccountPanelError.NONE
};
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';
}
const mapDispatchToProps = (dispatch: Dispatch): InactivePanelActionProps => ({
- startLinking: () => dispatch<any>(startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT))
+ startLinking: () => {
+ dispatch<any>(navigateToLinkAccount);
+ }
});
type InactivePanelProps = WithStyles<CssRules> & InactivePanelActionProps;
</Grid>
<Grid container item direction="row" spacing={24}>
<Grid item>
- <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
+ <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
Add another login to this account
</Button>
</Grid>
buildInfo: string;
uuidPrefix: string;
isNotLinking: boolean;
+ isLinkingPath: boolean;
}
type MainPanelRootProps = MainPanelRootDataProps & WithStyles<CssRules>;
export const MainPanelRoot = withStyles(styles)(
- ({ classes, loading, working, user, buildInfo, uuidPrefix, isNotLinking }: MainPanelRootProps) =>
+ ({ classes, loading, working, user, buildInfo, uuidPrefix, isNotLinking, isLinkingPath }: MainPanelRootProps) =>
loading
? <WorkbenchLoadingScreen />
- : <> { isNotLinking ? <>
- <MainAppBar
+ : <>
+ { isNotLinking && <MainAppBar
user={user}
buildInfo={buildInfo}
uuidPrefix={uuidPrefix}>
{working ? <LinearProgress color="secondary" /> : null}
- </MainAppBar>
+ </MainAppBar> }
<Grid container direction="column" className={classes.root}>
- { user ? (user.isActive ? <WorkbenchPanel /> : <InactivePanel />) : <LoginPanel />}
- </Grid>
- </> : user ? <LinkAccountPanel/> : <LoginPanel /> } </>
+ { user ? (user.isActive || (!user.isActive && isLinkingPath) ? <WorkbenchPanel isNotLinking={isNotLinking} isUserActive={user.isActive} /> : <InactivePanel />) : <LoginPanel /> }
+ </Grid>
+ </>
);
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 {
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
};
};
// 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";
}
});
-type WorkbenchPanelProps = WithStyles<CssRules>;
+interface WorkbenchDataProps {
+ isUserActive: boolean;
+ isNotLinking: boolean;
+}
+
+type WorkbenchPanelProps = WithStyles<CssRules> & WorkbenchDataProps;
const defaultSplitterSize = 90;
const saveSplitterSize = (size: number) => localStorage.setItem('splitterSize', size.toString());
export const WorkbenchPanel =
- withStyles(styles)(({ classes }: WorkbenchPanelProps) =>
- <Grid container item xs className={classes.root}>
- <Grid container item xs className={classes.container}>
- <SplitterLayout customClassName={classes.splitter} percentage={true}
+ withStyles(styles)((props: WorkbenchPanelProps) =>
+ <Grid container item xs className={props.classes.root}>
+ <Grid container item xs className={props.classes.container}>
+ <SplitterLayout customClassName={props.classes.splitter} percentage={true}
primaryIndex={0} primaryMinSize={10}
secondaryInitialSize={getSplitterInitialSize()} secondaryMinSize={40}
onSecondaryPaneSizeChange={saveSplitterSize}>
- <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
+ { props.isUserActive && props.isNotLinking && <Grid container item xs component='aside' direction='column' className={props.classes.asidePanel}>
<SidePanel />
- </Grid>
- <Grid container item xs component="main" direction="column" className={classes.contentWrapper}>
+ </Grid> }
+ <Grid container item xs component="main" direction="column" className={props.classes.contentWrapper}>
<Grid item xs>
- <MainContentBar />
+ { props.isNotLinking && <MainContentBar /> }
</Grid>
- <Grid item xs className={classes.content}>
+ <Grid item xs className={props.classes.content}>
<Switch>
<Route path={Routes.PROJECTS} component={ProjectPanel} />
<Route path={Routes.COLLECTIONS} component={CollectionPanel} />