[ARVADOS-WORKBENCH2] updated: 2.1.0-226-g44a5f696

Git user git at public.arvados.org
Tue Feb 23 19:39:44 UTC 2021


Summary of changes:
 cypress/integration/favorites.js                   |  53 -----
 cypress/integration/favorites.spec.js              | 222 +++++++++++++++++++++
 cypress/support/commands.js                        | 110 +++++-----
 .../common-service/common-resource-service.test.ts |  11 +-
 src/services/common-service/common-service.test.ts |  32 +++
 src/services/common-service/common-service.ts      |   9 +
 src/services/favorite-service/favorite-service.ts  |   4 +-
 src/store/auth/auth-action.test.ts                 |   8 +
 src/store/auth/auth-action.ts                      |  30 +--
 src/store/auth/auth-reducer.ts                     | 100 +++++-----
 src/store/collections/collection-info-actions.ts   |   4 +-
 .../collections/collection-partial-copy-actions.ts |  13 +-
 src/store/token-dialog/token-dialog-actions.tsx    |   4 +
 src/store/tree-picker/tree-picker-actions.ts       |  14 +-
 src/views-components/main-app-bar/account-menu.tsx |   7 +-
 .../projects-tree-picker/favorites-tree-picker.tsx |   4 +-
 .../generic-projects-tree-picker.tsx               |   8 +-
 .../projects-tree-picker/projects-tree-picker.tsx  |   5 +-
 .../projects-tree-picker/tree-picker-field.tsx     |   4 +-
 .../sharing-dialog/sharing-dialog-component.tsx    |   1 +
 .../sharing-invitation-form-component.tsx          |   4 +-
 src/views-components/token-dialog/token-dialog.tsx |   4 +
 22 files changed, 460 insertions(+), 191 deletions(-)
 delete mode 100644 cypress/integration/favorites.js
 create mode 100644 cypress/integration/favorites.spec.js
 create mode 100644 src/services/common-service/common-service.test.ts

       via  44a5f6968f1c2fb449dfb22e1742d2770662e6a9 (commit)
       via  becf93a5f9d1f7d96a3ce868c9b70c3d0094cf45 (commit)
       via  d70bcf2acdaaae12a403cdcf047e7514b145d6e4 (commit)
       via  1492885f32eefc4599f4f44228a6adf2ed7b8f7f (commit)
       via  eb40293bac1e910aacec5cd6d10941394e059a8f (commit)
       via  0c04dddf8855474ea629a6b55be38f15fbc4ac4c (commit)
       via  6a66e260c343d4943a68e5ca5ce205e83ba039c6 (commit)
       via  0429d37860f633725ecff6bd923e196a61121881 (commit)
       via  1301bb460ffc3e8d179492a08dbdb3a67aa080f8 (commit)
       via  40d96a9dafd0db3497a997a48ee223509de05de0 (commit)
       via  6e807fccb8a400d77b7e39f343f6ce634c40a922 (commit)
       via  b9f3923d7303b4f713dc248f5d96cb1d4db7a815 (commit)
       via  1d0bb953a83891cab9a5dc38eae8e60fad3ea42c (commit)
       via  cb320283ce44c2f31f2406e3c7de811689a7c9df (commit)
       via  16ff95d6cc0e7c643b9d7a8fc673481316df9378 (commit)
       via  4860e74d347552476c77e313c85502787b6d7dfe (commit)
       via  f60bd51d56ccbb4d6954ae407dbf992c26319e8e (commit)
       via  a45fc1ed0f5e0385e3741cca6c0b48284ae6f8bb (commit)
       via  87d9f6d4a643e5a5ec381f221d376b66c1f3cb29 (commit)
       via  b7fc86ba2cb07efa00e56c9d5536fc767166cfb5 (commit)
       via  decb7304af4d86f0aff7a2d7c87f869166111499 (commit)
      from  7d797c647a094822bc4f79be2715f47020e479d1 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 44a5f6968f1c2fb449dfb22e1742d2770662e6a9
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Tue Feb 23 16:30:26 2021 -0300

    16848: Adds expiration date to the "Get API Token" dialog.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/auth/auth-action.test.ts b/src/store/auth/auth-action.test.ts
index 79f93daa..f1a534c6 100644
--- a/src/store/auth/auth-action.test.ts
+++ b/src/store/auth/auth-action.test.ts
@@ -58,6 +58,12 @@ describe('auth-actions', () => {
                 prefs: {}
             });
 
+        axiosMock
+            .onGet("/api_client_authorizations/current")
+            .reply(200, {
+                expires_at: "2140-01-01T00:00:00.000Z"
+            });
+
         axiosMock
             .onGet("https://xc59z.arvadosapi.com/discovery/v1/apis/arvados/v1/rest")
             .reply(200, {
@@ -101,6 +107,7 @@ describe('auth-actions', () => {
                 try {
                     expect(auth).toEqual({
                         apiToken: "token",
+                        apiTokenExpiration: new Date("2140-01-01T00:00:00.000Z"),
                         config: {
                             apiRevision: 12345678,
                             clusterConfig: {
@@ -116,6 +123,7 @@ describe('auth-actions', () => {
                         },
                         sshKeys: [],
                         extraApiToken: undefined,
+                        extraApiTokenExpiration: undefined,
                         homeCluster: "zzzzz",
                         localCluster: "zzzzz",
                         loginCluster: undefined,
diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index faf098f7..2819364f 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -22,8 +22,8 @@ export const authActions = unionize({
     LOGIN: {},
     LOGOUT: ofType<{ deleteLinkData: boolean }>(),
     SET_CONFIG: ofType<{ config: Config }>(),
-    SET_EXTRA_TOKEN: ofType<{ extraToken: string }>(),
-    INIT_USER: ofType<{ user: User, token: string }>(),
+    SET_EXTRA_TOKEN: ofType<{ extraApiToken: string, extraApiTokenExpiration?: Date }>(),
+    INIT_USER: ofType<{ user: User, token: string, tokenExpiration?: Date }>(),
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>(),
     SET_SSH_KEYS: ofType<SshKeyResource[]>(),
@@ -88,7 +88,9 @@ export const saveApiToken = (token: string) => async (dispatch: Dispatch, getSta
     setAuthorizationHeader(svc, token);
     try {
         const user = await svc.authService.getUserDetails();
-        dispatch(authActions.INIT_USER({ user, token }));
+        const client = await svc.apiClientAuthorizationService.get('current');
+        const tokenExpiration = client.expiresAt ? new Date(client.expiresAt) : undefined;
+        dispatch(authActions.INIT_USER({ user, token, tokenExpiration }));
     } catch (e) {
         dispatch(authActions.LOGOUT({ deleteLinkData: false }));
     }
@@ -108,7 +110,10 @@ export const getNewExtraToken = (reuseStored: boolean = false) =>
             // allow token creation and there's no way to know that from workbench2 side in advance.
             const client = await services.apiClientAuthorizationService.create(undefined, false);
             const newExtraToken = getTokenV2(client);
-            dispatch(authActions.SET_EXTRA_TOKEN({ extraToken: newExtraToken }));
+            dispatch(authActions.SET_EXTRA_TOKEN({
+                extraApiToken: newExtraToken,
+                extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt): undefined,
+            }));
             return newExtraToken;
         } catch {
             console.warn("Cannot create new tokens with the current token, probably because of cluster's security settings.");
diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts
index d55e8301..7459b7ac 100644
--- a/src/store/auth/auth-reducer.ts
+++ b/src/store/auth/auth-reducer.ts
@@ -12,7 +12,9 @@ import { Config, mockConfig } from '~/common/config';
 export interface AuthState {
     user?: User;
     apiToken?: string;
+    apiTokenExpiration?: Date;
     extraApiToken?: string;
+    extraApiTokenExpiration?: Date;
     sshKeys: SshKeyResource[];
     sessions: Session[];
     localCluster: string;
@@ -26,7 +28,9 @@ export interface AuthState {
 const initialState: AuthState = {
     user: undefined,
     apiToken: undefined,
+    apiTokenExpiration: undefined,
     extraApiToken: undefined,
+    extraApiTokenExpiration: undefined,
     sshKeys: [],
     sessions: [],
     localCluster: "",
@@ -39,70 +43,66 @@ const initialState: AuthState = {
 
 export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
     return authActions.match(action, {
-        SET_CONFIG: ({ config }) => {
-            return {
+        SET_CONFIG: ({ config }) =>
+            ({
                 ...state,
                 config,
                 localCluster: config.uuidPrefix,
-                remoteHosts: { ...config.remoteHosts, [config.uuidPrefix]: new URL(config.rootUrl).host },
+                remoteHosts: {
+                    ...config.remoteHosts,
+                    [config.uuidPrefix]: new URL(config.rootUrl).host
+                },
                 homeCluster: config.loginCluster || config.uuidPrefix,
                 loginCluster: config.loginCluster,
-                remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config }
-            };
-        },
-        REMOTE_CLUSTER_CONFIG: ({ config }) => {
-            return {
+                remoteHostsConfig: {
+                    ...state.remoteHostsConfig,
+                    [config.uuidPrefix]: config
+                }
+            }),
+        REMOTE_CLUSTER_CONFIG: ({ config }) =>
+            ({
                 ...state,
-                remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config },
-            };
-        },
-        SET_EXTRA_TOKEN: ({ extraToken }) => ({ ...state, extraApiToken: extraToken }),
-        INIT_USER: ({ user, token }) => {
-            return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) };
-        },
-        LOGIN: () => {
-            return state;
-        },
-        LOGOUT: () => {
-            return { ...state, apiToken: undefined };
-        },
-        USER_DETAILS_SUCCESS: (user: User) => {
-            return { ...state, user, homeCluster: user.uuid.substr(0, 5) };
-        },
-        SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => {
-            return { ...state, sshKeys };
-        },
-        ADD_SSH_KEY: (sshKey: SshKeyResource) => {
-            return { ...state, sshKeys: state.sshKeys.concat(sshKey) };
-        },
-        REMOVE_SSH_KEY: (uuid: string) => {
-            return { ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid) };
-        },
-        SET_HOME_CLUSTER: (homeCluster: string) => {
-            return { ...state, homeCluster };
-        },
-        SET_SESSIONS: (sessions: Session[]) => {
-            return { ...state, sessions };
-        },
-        ADD_SESSION: (session: Session) => {
-            return { ...state, sessions: state.sessions.concat(session) };
-        },
-        REMOVE_SESSION: (clusterId: string) => {
-            return {
+                remoteHostsConfig: {
+                    ...state.remoteHostsConfig,
+                    [config.uuidPrefix]: config
+                },
+            }),
+        SET_EXTRA_TOKEN: ({ extraApiToken, extraApiTokenExpiration }) =>
+            ({ ...state, extraApiToken, extraApiTokenExpiration }),
+        INIT_USER: ({ user, token, tokenExpiration }) =>
+            ({ ...state,
+                user,
+                apiToken: token,
+                apiTokenExpiration: tokenExpiration,
+                homeCluster: user.uuid.substr(0, 5)
+            }),
+        LOGIN: () => state,
+        LOGOUT: () => ({ ...state, apiToken: undefined }),
+        USER_DETAILS_SUCCESS: (user: User) =>
+            ({ ...state, user, homeCluster: user.uuid.substr(0, 5) }),
+        SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => ({ ...state, sshKeys }),
+        ADD_SSH_KEY: (sshKey: SshKeyResource) =>
+            ({ ...state, sshKeys: state.sshKeys.concat(sshKey) }),
+        REMOVE_SSH_KEY: (uuid: string) =>
+            ({ ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid) }),
+        SET_HOME_CLUSTER: (homeCluster: string) => ({ ...state, homeCluster }),
+        SET_SESSIONS: (sessions: Session[]) => ({ ...state, sessions }),
+        ADD_SESSION: (session: Session) =>
+            ({ ...state, sessions: state.sessions.concat(session) }),
+        REMOVE_SESSION: (clusterId: string) =>
+            ({
                 ...state,
                 sessions: state.sessions.filter(
                     session => session.clusterId !== clusterId
                 )
-            };
-        },
-        UPDATE_SESSION: (session: Session) => {
-            return {
+            }),
+        UPDATE_SESSION: (session: Session) =>
+            ({
                 ...state,
                 sessions: state.sessions.map(
                     s => s.clusterId === session.clusterId ? session : s
                 )
-            };
-        },
+            }),
         default: () => state
     });
 };
diff --git a/src/store/token-dialog/token-dialog-actions.tsx b/src/store/token-dialog/token-dialog-actions.tsx
index 2cf573bc..c6eb0145 100644
--- a/src/store/token-dialog/token-dialog-actions.tsx
+++ b/src/store/token-dialog/token-dialog-actions.tsx
@@ -12,6 +12,7 @@ const API_HOST_PROPERTY_NAME = 'apiHost';
 
 export interface TokenDialogData {
     token: string;
+    tokenExpiration?: Date;
     apiHost: string;
     canCreateNewTokens: boolean;
 }
@@ -26,6 +27,9 @@ export const getTokenDialogData = (state: RootState): TokenDialogData => {
     return {
         apiHost: getProperty<string>(API_HOST_PROPERTY_NAME)(state.properties) || '',
         token: state.auth.extraApiToken || state.auth.apiToken || '',
+        tokenExpiration: state.auth.extraApiToken
+            ? state.auth.extraApiTokenExpiration
+            : state.auth.apiTokenExpiration,
         canCreateNewTokens,
     };
 };
diff --git a/src/views-components/token-dialog/token-dialog.tsx b/src/views-components/token-dialog/token-dialog.tsx
index 5bbcaf57..60ef360c 100644
--- a/src/views-components/token-dialog/token-dialog.tsx
+++ b/src/views-components/token-dialog/token-dialog.tsx
@@ -111,6 +111,10 @@ unset ARVADOS_API_HOST_INSECURE`
                     Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your account.
                 </Typography>
                 <DefaultCodeSnippet lines={[this.getSnippet(data)]} />
+                { data.tokenExpiration
+                    ? <Typography component='span'>Expires at: {data.tokenExpiration.toLocaleString()}</Typography>
+                    : null
+                }
                 <CopyToClipboard text={this.getSnippet(data)} onCopy={() => this.onCopy('Token copied to clipboard')}>
                     <Button
                         color="primary"

commit becf93a5f9d1f7d96a3ce868c9b70c3d0094cf45
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Tue Feb 23 13:10:47 2021 -0300

    16848: Only request an extra token when needed.
    
    Instead of preemptively asking for the extra token at session init, ask for
    one every time it's needed, avoiding creating unused tokens.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index fb94746f..faf098f7 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -82,22 +82,23 @@ export const getConfig = (dispatch: Dispatch, getState: () => RootState, service
     return state.remoteHostsConfig[state.localCluster];
 };
 
-export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+export const saveApiToken = (token: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
     const config = dispatch<any>(getConfig);
     const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } });
     setAuthorizationHeader(svc, token);
-    return svc.authService.getUserDetails().then((user: User) => {
+    try {
+        const user = await svc.authService.getUserDetails();
         dispatch(authActions.INIT_USER({ user, token }));
-        // Upon user init, request an extra token that won't be expired on logout
-        // for other uses like the "get token" dialog, or S3 URL building.
-        dispatch<any>(getNewExtraToken());
-    }).catch(() => {
+    } catch (e) {
         dispatch(authActions.LOGOUT({ deleteLinkData: false }));
-    });
+    }
 };
 
-export const getNewExtraToken = () =>
+export const getNewExtraToken = (reuseStored: boolean = false) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        if (reuseStored && getState().auth.extraApiToken !== undefined) {
+            return getState().auth.extraApiToken;
+        }
         const user = getState().auth.user;
         const loginCluster = getState().auth.config.clusterConfig.Login.LoginCluster;
         if (user === undefined) { return; }
diff --git a/src/store/collections/collection-info-actions.ts b/src/store/collections/collection-info-actions.ts
index b904da70..29dc6b87 100644
--- a/src/store/collections/collection-info-actions.ts
+++ b/src/store/collections/collection-info-actions.ts
@@ -6,6 +6,7 @@ import { Dispatch } from "redux";
 import { RootState } from "~/store/store";
 import { ServiceRepository } from "~/services/services";
 import { dialogActions } from '~/store/dialog/dialog-actions';
+import { getNewExtraToken } from "../auth/auth-action";
 
 export const COLLECTION_WEBDAV_S3_DIALOG_NAME = 'collectionWebdavS3Dialog';
 
@@ -21,7 +22,8 @@ export interface WebDavS3InfoDialogData {
 }
 
 export const openWebDavS3InfoDialog = (uuid: string, activeTab?: number) =>
-    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        await dispatch<any>(getNewExtraToken(true));
         dispatch(dialogActions.OPEN_DIALOG({
             id: COLLECTION_WEBDAV_S3_DIALOG_NAME,
             data: {
diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx
index 58ed7b84..ea3a2dd9 100644
--- a/src/views-components/main-app-bar/account-menu.tsx
+++ b/src/views-components/main-app-bar/account-menu.tsx
@@ -9,7 +9,7 @@ import { User, getUserDisplayName } from "~/models/user";
 import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
 import { UserPanelIcon } from "~/components/icon/icon";
 import { DispatchProp, connect } from 'react-redux';
-import { authActions } from '~/store/auth/auth-action';
+import { authActions, getNewExtraToken } from '~/store/auth/auth-action';
 import { RootState } from "~/store/store";
 import { openTokenDialog } from '~/store/token-dialog/token-dialog-actions';
 import { openRepositoriesPanel } from "~/store/repositories/repositories-actions";
@@ -70,7 +70,10 @@ export const AccountMenuComponent =
             {user.isActive ? <>
                 <MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
                 {!user.isAdmin && <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>}
-                <MenuItem onClick={() => dispatch(openTokenDialog)}>Get API token</MenuItem>
+                <MenuItem onClick={() => {
+                    dispatch<any>(getNewExtraToken(true));
+                    dispatch(openTokenDialog);
+                }}>Get API token</MenuItem>
                 <MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
                 <MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
                 <MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>

commit d70bcf2acdaaae12a403cdcf047e7514b145d6e4
Merge: 7d797c64 1492885f
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Feb 22 18:04:57 2021 -0300

    Merge branch 'master' into 16848-token-handling-improvements
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>


-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list