15088: Adds routing for link-account panel
authorEric Biagiotti <ebiagiotti@veritasgenetics.com>
Tue, 23 Apr 2019 15:09:35 +0000 (11:09 -0400)
committerEric Biagiotti <ebiagiotti@veritasgenetics.com>
Thu, 25 Apr 2019 14:37:38 +0000 (10:37 -0400)
Also adds a link in the my-account panel to access the link-account panel

Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <ebiagiotti@veritasgenetics.com>

src/routes/route-change-handlers.ts
src/routes/routes.ts
src/store/link-account/link-account-panel-actions.ts [new file with mode: 0644]
src/store/my-account/my-account-panel-actions.ts
src/store/navigation/navigation-action.ts
src/store/workbench/workbench-actions.ts
src/views/my-account-panel/my-account-panel-root.tsx
src/views/my-account-panel/my-account-panel.tsx

index 2811f95aab33525e4e483086989ce3c46280dae4..e0a51550f745eeb2fd9ab125165a3ad8885b6aad 100644 (file)
@@ -41,6 +41,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     const computeNodesMatch = Routes.matchComputeNodesRoute(pathname);
     const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname);
     const myAccountMatch = Routes.matchMyAccountRoute(pathname);
     const computeNodesMatch = Routes.matchComputeNodesRoute(pathname);
     const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname);
     const myAccountMatch = Routes.matchMyAccountRoute(pathname);
+    const linkAccountMatch = Routes.matchLinkAccountRoute(pathname);
     const userMatch = Routes.matchUsersRoute(pathname);
     const groupsMatch = Routes.matchGroupsRoute(pathname);
     const groupDetailsMatch = Routes.matchGroupDetailsRoute(pathname);
     const userMatch = Routes.matchUsersRoute(pathname);
     const groupsMatch = Routes.matchGroupsRoute(pathname);
     const groupDetailsMatch = Routes.matchGroupDetailsRoute(pathname);
@@ -94,6 +95,8 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
         store.dispatch(WorkbenchActions.loadApiClientAuthorizations);
     } else if (myAccountMatch) {
         store.dispatch(WorkbenchActions.loadMyAccount);
         store.dispatch(WorkbenchActions.loadApiClientAuthorizations);
     } else if (myAccountMatch) {
         store.dispatch(WorkbenchActions.loadMyAccount);
+    } else if (linkAccountMatch) {
+        store.dispatch(WorkbenchActions.loadLinkAccount);
     } else if (userMatch) {
         store.dispatch(WorkbenchActions.loadUsers);
     } else if (groupsMatch) {
     } else if (userMatch) {
         store.dispatch(WorkbenchActions.loadUsers);
     } else if (groupsMatch) {
index 3fd6670d8899814662b413e0898b94191dbe46a1..02835fcce843c6fd894d072197542b6123abd9ec 100644 (file)
@@ -27,6 +27,7 @@ export const Routes = {
     SSH_KEYS_USER: `/ssh-keys-user`,
     SITE_MANAGER: `/site-manager`,
     MY_ACCOUNT: '/my-account',
     SSH_KEYS_USER: `/ssh-keys-user`,
     SITE_MANAGER: `/site-manager`,
     MY_ACCOUNT: '/my-account',
+    LINK_ACCOUNT: '/link_account',
     KEEP_SERVICES: `/keep-services`,
     COMPUTE_NODES: `/nodes`,
     USERS: '/users',
     KEEP_SERVICES: `/keep-services`,
     COMPUTE_NODES: `/nodes`,
     USERS: '/users',
@@ -115,6 +116,9 @@ export const matchSiteManagerRoute = (route: string) =>
 export const matchMyAccountRoute = (route: string) =>
     matchPath(route, { path: Routes.MY_ACCOUNT });
 
 export const matchMyAccountRoute = (route: string) =>
     matchPath(route, { path: Routes.MY_ACCOUNT });
 
+export const matchLinkAccountRoute = (route: string) =>
+    matchPath(route, { path: Routes.LINK_ACCOUNT });
+
 export const matchKeepServicesRoute = (route: string) =>
     matchPath(route, { path: Routes.KEEP_SERVICES });
 
 export const matchKeepServicesRoute = (route: string) =>
     matchPath(route, { path: Routes.KEEP_SERVICES });
 
diff --git a/src/store/link-account/link-account-panel-actions.ts b/src/store/link-account/link-account-panel-actions.ts
new file mode 100644 (file)
index 0000000..5139752
--- /dev/null
@@ -0,0 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { RootState } from "~/store/store";
+import { initialize } from "redux-form";
+import { ServiceRepository } from "~/services/services";
+import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
+import { authActions } from "~/store/auth/auth-action";
+import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
+
+export const LINK_ACCOUNT_FORM = 'linkAccountForm';
+
+export const loadLinkAccountPanel = () =>
+    (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+       dispatch(setBreadcrumbs([{ label: 'Link account'}]));
+    };
index 34bb2693dbb7b55796767cce66efdd93a5b93403..294f77f6329da3ddf5cf6369e7a6391af0b9b849 100644 (file)
@@ -9,6 +9,7 @@ import { ServiceRepository } from "~/services/services";
 import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
 import { authActions } from "~/store/auth/auth-action";
 import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
 import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
 import { authActions } from "~/store/auth/auth-action";
 import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
+import { navigateToLinkAccount } from '~/store/navigation/navigation-action';
 
 export const MY_ACCOUNT_FORM = 'myAccountForm';
 
 
 export const MY_ACCOUNT_FORM = 'myAccountForm';
 
@@ -17,6 +18,11 @@ export const loadMyAccountPanel = () =>
         dispatch(setBreadcrumbs([{ label: 'User profile'}]));
     };
 
         dispatch(setBreadcrumbs([{ label: 'User profile'}]));
     };
 
+export const openLinkAccount = () =>
+    (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+        dispatch<any>(navigateToLinkAccount);
+    };
+
 export const saveEditedUser = (resource: any) =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         try {
 export const saveEditedUser = (resource: any) =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         try {
index af7a0c037357d1104e70c62d84769f044288cfd3..3bec160992a92ae4342e3789321e57cf7ab2471c 100644 (file)
@@ -85,6 +85,8 @@ export const navigateToSiteManager= push(Routes.SITE_MANAGER);
 
 export const navigateToMyAccount = push(Routes.MY_ACCOUNT);
 
 
 export const navigateToMyAccount = push(Routes.MY_ACCOUNT);
 
+export const navigateToLinkAccount = push(Routes.LINK_ACCOUNT);
+
 export const navigateToKeepServices = push(Routes.KEEP_SERVICES);
 
 export const navigateToComputeNodes = push(Routes.COMPUTE_NODES);
 export const navigateToKeepServices = push(Routes.KEEP_SERVICES);
 
 export const navigateToComputeNodes = push(Routes.COMPUTE_NODES);
index adf3fa15aeeb416f927ed99a3f483e4ae7894325..10f86a68087e08d64505eaae5eb2c0ef3d228ab1 100644 (file)
@@ -59,6 +59,7 @@ import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
 import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
 import { loadSshKeysPanel } from '~/store/auth/auth-action-ssh';
 import { loadMyAccountPanel } from '~/store/my-account/my-account-panel-actions';
 import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
 import { loadSshKeysPanel } from '~/store/auth/auth-action-ssh';
 import { loadMyAccountPanel } from '~/store/my-account/my-account-panel-actions';
+import { loadLinkAccountPanel } from '~/store/link-account/link-account-panel-actions';
 import { loadSiteManagerPanel } from '~/store/auth/auth-action-session';
 import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
 import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
 import { loadSiteManagerPanel } from '~/store/auth/auth-action-session';
 import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
 import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
@@ -485,6 +486,11 @@ export const loadMyAccount = handleFirstTimeLoad(
         dispatch(loadMyAccountPanel());
     });
 
         dispatch(loadMyAccountPanel());
     });
 
+export const loadLinkAccount = handleFirstTimeLoad(
+    (dispatch: Dispatch<any>) => {
+        dispatch(loadLinkAccountPanel());
+    });
+
 export const loadKeepServices = handleFirstTimeLoad(
     async (dispatch: Dispatch<any>) => {
         await dispatch(loadKeepServicesPanel());
 export const loadKeepServices = handleFirstTimeLoad(
     async (dispatch: Dispatch<any>) => {
         await dispatch(loadKeepServicesPanel());
index e84b3b6484d8ba46f57e31c5e3ddc5222b164489..7dcbe09a57ac6d531403deb42173a694c2b38f46 100644 (file)
@@ -6,6 +6,7 @@ import * as React from 'react';
 import { Field, InjectedFormProps, WrappedFieldProps } from "redux-form";
 import { TextField } from "~/components/text-field/text-field";
 import { NativeSelectField } from "~/components/select-field/select-field";
 import { Field, InjectedFormProps, WrappedFieldProps } from "redux-form";
 import { TextField } from "~/components/text-field/text-field";
 import { NativeSelectField } from "~/components/select-field/select-field";
+import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
 import {
     StyleRulesCallback,
     WithStyles,
 import {
     StyleRulesCallback,
     WithStyles,
@@ -21,7 +22,7 @@ import { ArvadosTheme } from '~/common/custom-theme';
 import { User } from "~/models/user";
 import { MY_ACCOUNT_VALIDATION } from "~/validators/validators";
 
 import { User } from "~/models/user";
 import { MY_ACCOUNT_VALIDATION } from "~/validators/validators";
 
-type CssRules = 'root' | 'gridItem' | 'label' | 'title' | 'actions';
+type CssRules = 'root' | 'gridItem' | 'label' | 'title' | 'actions' | 'link';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
@@ -39,13 +40,24 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         marginBottom: theme.spacing.unit * 3,
         color: theme.palette.grey["600"]
     },
         marginBottom: theme.spacing.unit * 3,
         color: theme.palette.grey["600"]
     },
+    link: {
+        lineHeight:'2.1',
+        whiteSpace: 'nowrap',
+        fontSize: '1rem',
+        color: theme.palette.primary.main,
+        '&:hover': {
+            cursor: 'pointer'
+        }
+    },
     actions: {
         display: 'flex',
         justifyContent: 'flex-end'
     }
 });
 
     actions: {
         display: 'flex',
         justifyContent: 'flex-end'
     }
 });
 
-export interface MyAccountPanelRootActionProps { }
+export interface MyAccountPanelRootActionProps {
+    openLinkAccount: () => void;
+ }
 
 export interface MyAccountPanelRootDataProps {
     isPristine: boolean;
 
 export interface MyAccountPanelRootDataProps {
     isPristine: boolean;
@@ -64,7 +76,7 @@ const RoleTypes = [
     { key: 'Other', value: 'Other' }
 ];
 
     { key: 'Other', value: 'Other' }
 ];
 
-type MyAccountPanelRootProps = InjectedFormProps<MyAccountPanelRootActionProps> & MyAccountPanelRootDataProps & WithStyles<CssRules>;
+type MyAccountPanelRootProps = InjectedFormProps & MyAccountPanelRootActionProps & MyAccountPanelRootDataProps & WithStyles<CssRules>;
 
 type LocalClusterProp = { localCluster: string };
 const renderField: React.ComponentType<WrappedFieldProps & LocalClusterProp> = ({ input, localCluster }) => (
 
 type LocalClusterProp = { localCluster: string };
 const renderField: React.ComponentType<WrappedFieldProps & LocalClusterProp> = ({ input, localCluster }) => (
@@ -72,12 +84,21 @@ const renderField: React.ComponentType<WrappedFieldProps & LocalClusterProp> = (
 );
 
 export const MyAccountPanelRoot = withStyles(styles)(
 );
 
 export const MyAccountPanelRoot = withStyles(styles)(
-    ({ classes, isValid, handleSubmit, reset, isPristine, invalid, submitting, localCluster }: MyAccountPanelRootProps) => {
+    ({ classes, isValid, handleSubmit, reset, isPristine, invalid, submitting, localCluster, openLinkAccount}: MyAccountPanelRootProps) => {
         return <Card className={classes.root}>
             <CardContent>
         return <Card className={classes.root}>
             <CardContent>
-                <Typography variant="title" className={classes.title}>
-                    Logged in as <Field name="uuid" component={renderField} localCluster={localCluster} />
-                </Typography>
+                <Grid container spacing={24}>
+                    <Grid item className={classes.gridItem}>
+                        <Typography variant="title" className={classes.title}>
+                            Logged in as <Field name="uuid" component={renderField} localCluster={localCluster} />
+                        </Typography>
+                    </Grid>
+                    <Grid item className={classes.gridItem}>
+                        <span onClick={() => openLinkAccount()}>
+                            <DetailsAttribute classLabel={classes.link} label='Link account' />
+                        </span>
+                    </Grid>
+                </Grid>
                 <form onSubmit={handleSubmit}>
                     <Grid container spacing={24}>
                         <Grid item className={classes.gridItem} sm={6} xs={12}>
                 <form onSubmit={handleSubmit}>
                     <Grid container spacing={24}>
                         <Grid item className={classes.gridItem} sm={6} xs={12}>
index bd1f58745724b9dbd0a87eb083d31c0dd48fe135..85f285d60f208798eaafafae226a9c15a7f2b6a4 100644 (file)
@@ -3,11 +3,12 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { RootState } from '~/store/store';
 // SPDX-License-Identifier: AGPL-3.0
 
 import { RootState } from '~/store/store';
+import { Dispatch } from 'redux';
 import { compose } from 'redux';
 import { reduxForm, isPristine, isValid } from 'redux-form';
 import { connect } from 'react-redux';
 import { compose } from 'redux';
 import { reduxForm, isPristine, isValid } from 'redux-form';
 import { connect } from 'react-redux';
-import { saveEditedUser } from '~/store/my-account/my-account-panel-actions';
-import { MyAccountPanelRoot, MyAccountPanelRootDataProps } from '~/views/my-account-panel/my-account-panel-root';
+import { saveEditedUser, openLinkAccount } from '~/store/my-account/my-account-panel-actions';
+import { MyAccountPanelRoot, MyAccountPanelRootDataProps, MyAccountPanelRootActionProps } from '~/views/my-account-panel/my-account-panel-root';
 import { MY_ACCOUNT_FORM } from "~/store/my-account/my-account-panel-actions";
 
 const mapStateToProps = (state: RootState): MyAccountPanelRootDataProps => ({
 import { MY_ACCOUNT_FORM } from "~/store/my-account/my-account-panel-actions";
 
 const mapStateToProps = (state: RootState): MyAccountPanelRootDataProps => ({
@@ -17,8 +18,12 @@ const mapStateToProps = (state: RootState): MyAccountPanelRootDataProps => ({
     localCluster: state.auth.localCluster
 });
 
     localCluster: state.auth.localCluster
 });
 
+const mapDispatchToProps = (dispatch: Dispatch): MyAccountPanelRootActionProps => ({
+    openLinkAccount: () => dispatch<any>(openLinkAccount())
+});
+
 export const MyAccountPanel = compose(
 export const MyAccountPanel = compose(
-    connect(mapStateToProps),
+    connect(mapStateToProps, mapDispatchToProps),
     reduxForm({
         form: MY_ACCOUNT_FORM,
         onSubmit: (data, dispatch) => {
     reduxForm({
         form: MY_ACCOUNT_FORM,
         onSubmit: (data, dispatch) => {