15803: INIT->INIT_USER, CONFIG->SET_CONFIG, remove config reducer 15803-unsetup
authorPeter Amstutz <pamstutz@veritasgenetics.com>
Wed, 27 Nov 2019 19:37:10 +0000 (14:37 -0500)
committerPeter Amstutz <pamstutz@veritasgenetics.com>
Wed, 27 Nov 2019 19:37:10 +0000 (14:37 -0500)
I realized there was mostly do-nothing "config" reducer, whereas most
all the other cluster configuration was stored on the "auth" reducer,
so I moved config over to auth and got rid of the config reducer.

Added some comments to auth-middleware.ts.

Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

17 files changed:
src/store/auth/auth-action.test.ts
src/store/auth/auth-action.ts
src/store/auth/auth-middleware.ts
src/store/auth/auth-reducer.test.ts
src/store/auth/auth-reducer.ts
src/store/config/config-action.ts [deleted file]
src/store/config/config-reducer.ts [deleted file]
src/store/link-account-panel/link-account-panel-actions.ts
src/store/public-favorites-panel/public-favorites-middleware-service.ts
src/store/public-favorites/public-favorites-actions.ts
src/store/store.ts
src/store/tree-picker/tree-picker-actions.ts
src/store/users/users-actions.ts
src/views-components/main-app-bar/account-menu.tsx
src/views/inactive-panel/inactive-panel.tsx
src/views/login-panel/login-panel.tsx
src/views/main-panel/main-panel.tsx

index f721e76033582ddaa475d38df2f919e11a323565..f7aa5c4c9f630abcddbbe1355cb90368f8005b0b 100644 (file)
@@ -81,6 +81,13 @@ describe('auth-actions', () => {
                 try {
                     expect(auth).toEqual({
                         apiToken: "token",
+                        config: {
+                            remoteHosts: {
+                                "xc59z": "xc59z.arvadosapi.com",
+                            },
+                            rootUrl: "https://zzzzz.arvadosapi.com",
+                            uuidPrefix: "zzzzz",
+                        },
                         sshKeys: [],
                         homeCluster: "zzzzz",
                         localCluster: "zzzzz",
index 8fb5c5e9d2c2b280a1a3fd595afa002dd6adcfca..1d8a01c6f61ef1ee6562012cc184d17a0ea732eb 100644 (file)
@@ -19,8 +19,8 @@ import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions';
 export const authActions = unionize({
     LOGIN: {},
     LOGOUT: ofType<{ deleteLinkData: boolean }>(),
-    CONFIG: ofType<{ config: Config }>(),
-    INIT: ofType<{ user: User, token: string }>(),
+    SET_CONFIG: ofType<{ config: Config }>(),
+    INIT_USER: ofType<{ user: User, token: string }>(),
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>(),
     SET_SSH_KEYS: ofType<SshKeyResource[]>(),
@@ -54,7 +54,7 @@ const init = (config: Config) => (dispatch: Dispatch, getState: () => RootState,
     if (homeCluster && !config.remoteHosts[homeCluster]) {
         homeCluster = undefined;
     }
-    dispatch(authActions.CONFIG({ config }));
+    dispatch(authActions.SET_CONFIG({ config }));
     dispatch(authActions.SET_HOME_CLUSTER(config.loginCluster || homeCluster || config.uuidPrefix));
 
     if (token && token !== "undefined") {
@@ -77,7 +77,7 @@ export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: ()
     const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } });
     setAuthorizationHeader(svc, token);
     return svc.authService.getUserDetails().then((user: User) => {
-        dispatch(authActions.INIT({ user, token }));
+        dispatch(authActions.INIT_USER({ user, token }));
     });
 };
 
index 817ddd2e21a6669ab1589649554ad65b0b8dd2e4..76f85984b06e0a5dc0a5c88696f8ce931eb0286d 100644 (file)
@@ -12,9 +12,17 @@ import { progressIndicatorActions } from "~/store/progress-indicator/progress-in
 import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions';
 
 export const authMiddleware = (services: ServiceRepository): Middleware => store => next => action => {
+    // Middleware to update external state (local storage, window
+    // title) to ensure that they stay in sync with redux state.
+
     authActions.match(action, {
-        INIT: ({ user, token }) => {
+        INIT_USER: ({ user, token }) => {
+            // The "next" method passes the action to the next
+            // middleware in the chain, or the reducer.  That means
+            // after next() returns, the action has (presumably) been
+            // applied by the reducer to update the state.
             next(action);
+
             const state: RootState = store.getState();
 
             if (state.auth.apiToken) {
@@ -27,16 +35,26 @@ export const authMiddleware = (services: ServiceRepository): Middleware => store
 
             store.dispatch<any>(initSessions(services.authService, state.auth.remoteHostsConfig[state.auth.localCluster], user));
             if (!user.isActive) {
+                // As a special case, if the user is inactive, they
+                // may be able to self-activate using the "activate"
+                // method.  Note, for this to work there can't be any
+                // unsigned user agreements, we assume the API server is just going to
+                // rubber-stamp our activation request.  At some point in the future we'll
+                // want to either add support for displaying/signing user
+                // agreements or get rid of self-activation.
+                // For more details, see:
+                // https://doc.arvados.org/master/admin/user-management.html
+
                 store.dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
                 services.userService.activate(user.uuid).then((user: User) => {
-                    store.dispatch(authActions.INIT({ user, token }));
+                    store.dispatch(authActions.INIT_USER({ user, token }));
                     store.dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
                 }).catch(() => {
                     store.dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
                 });
             }
         },
-        CONFIG: ({ config }) => {
+        SET_CONFIG: ({ config }) => {
             document.title = `Arvados Workbench (${config.uuidPrefix})`;
             next(action);
         },
index e862a313d0f3dbbeddea3e71c36dbf68247ccb07..756feeeb6b835d7c3c845c88752a0aa6726dd6db 100644 (file)
@@ -35,9 +35,10 @@ describe('auth-reducer', () => {
             isAdmin: false,
             isActive: true
         };
-        const state = reducer(initialState, authActions.INIT({ user, token: "token" }));
+        const state = reducer(initialState, authActions.INIT_USER({ user, token: "token" }));
         expect(state).toEqual({
             apiToken: "token",
+            config: mockConfig({}),
             user,
             sshKeys: [],
             sessions: [],
@@ -67,6 +68,7 @@ describe('auth-reducer', () => {
         const state = reducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
         expect(state).toEqual({
             apiToken: undefined,
+            config: mockConfig({}),
             sshKeys: [],
             sessions: [],
             homeCluster: "uuid",
index da3c223f75d38e2872e54e9df452915e16d358ba..946407fe24172610fbc3aaf9cff7b95052a43af8 100644 (file)
@@ -7,7 +7,7 @@ import { User } from "~/models/user";
 import { ServiceRepository } from "~/services/services";
 import { SshKeyResource } from '~/models/ssh-key';
 import { Session } from "~/models/session";
-import { Config } from '~/common/config';
+import { Config, mockConfig } from '~/common/config';
 
 export interface AuthState {
     user?: User;
@@ -19,6 +19,7 @@ export interface AuthState {
     loginCluster: string;
     remoteHosts: { [key: string]: string };
     remoteHostsConfig: { [key: string]: Config };
+    config: Config;
 }
 
 const initialState: AuthState = {
@@ -30,14 +31,16 @@ const initialState: AuthState = {
     homeCluster: "",
     loginCluster: "",
     remoteHosts: {},
-    remoteHostsConfig: {}
+    remoteHostsConfig: {},
+    config: mockConfig({})
 };
 
 export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
     return authActions.match(action, {
-        CONFIG: ({ config }) => {
+        SET_CONFIG: ({ config }) => {
             return {
                 ...state,
+                config,
                 localCluster: config.uuidPrefix,
                 remoteHosts: { ...config.remoteHosts, [config.uuidPrefix]: new URL(config.rootUrl).host },
                 homeCluster: config.loginCluster || config.uuidPrefix,
@@ -51,7 +54,7 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat
                 remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config },
             };
         },
-        INIT: ({ user, token }) => {
+        INIT_USER: ({ user, token }) => {
             return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) };
         },
         LOGIN: () => {
diff --git a/src/store/config/config-action.ts b/src/store/config/config-action.ts
deleted file mode 100644 (file)
index fd79294..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { ofType, unionize, UnionOf } from '~/common/unionize';
-import { Config } from '~/common/config';
-
-export const configActions = unionize({
-    CONFIG: ofType<{ config: Config }>(),
-});
-
-export type ConfigAction = UnionOf<typeof configActions>;
diff --git a/src/store/config/config-reducer.ts b/src/store/config/config-reducer.ts
deleted file mode 100644 (file)
index f0b76b1..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { configActions, ConfigAction } from "./config-action";
-import { mockConfig } from '~/common/config';
-
-export const configReducer = (state = mockConfig({}), action: ConfigAction) => {
-    return configActions.match(action, {
-        CONFIG: ({ config }) => {
-            return {
-                ...state, ...config
-            };
-        },
-        default: () => state
-    });
-};
index 6540ee696ac33f464f9c888bda080c98dffaaa44..1e94fcfacaf46d44469a72b4f616c54fb432e541 100644 (file)
@@ -91,7 +91,7 @@ export const checkForLinkStatus = () =>
 
 export const switchUser = (user: UserResource, token: string) =>
     (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(authActions.INIT({ user, token }));
+        dispatch(authActions.INIT_USER({ user, token }));
     };
 
 export const linkFailed = () =>
index be7f5285953df1fcf0e2feb36b01792a95cd3174..a15fe97542713b9bdec1c80729641d9db05f5e4b 100644 (file)
@@ -53,7 +53,7 @@ export class PublicFavoritesMiddlewareService extends DataExplorerMiddlewareServ
             }
             try {
                 api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
-                const uuidPrefix = api.getState().config.uuidPrefix;
+                const uuidPrefix = api.getState().auth.config.uuidPrefix;
                 const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
                 const responseLinks = await this.services.linkService.list({
                     limit: dataExplorer.rowsPerPage,
@@ -129,4 +129,4 @@ const couldNotFetchPublicFavorites = () =>
     snackbarActions.OPEN_SNACKBAR({
         message: 'Could not fetch public favorites contents.',
         kind: SnackbarKind.ERROR
-    });
\ No newline at end of file
+    });
index 50b9070baf0987ab6ec64c4624cf301dee96d02d..d5a5cd46264778a82913adeb88866aa72dc08c04 100644 (file)
@@ -21,7 +21,7 @@ export type PublicFavoritesAction = UnionOf<typeof publicFavoritesActions>;
 export const togglePublicFavorite = (resource: { uuid: string; name: string }) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
         dispatch(progressIndicatorActions.START_WORKING("togglePublicFavorite"));
-        const uuidPrefix = getState().config.uuidPrefix;
+        const uuidPrefix = getState().auth.config.uuidPrefix;
         const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
         dispatch(publicFavoritesActions.TOGGLE_PUBLIC_FAVORITE({ resourceUuid: resource.uuid }));
         const isPublicFavorite = checkPublicFavorite(resource.uuid, getState().publicFavorites);
@@ -57,7 +57,7 @@ export const togglePublicFavorite = (resource: { uuid: string; name: string }) =
 
 export const updatePublicFavorites = (resourceUuids: string[]) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const uuidPrefix = getState().config.uuidPrefix;
+        const uuidPrefix = getState().auth.config.uuidPrefix;
         const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
         dispatch(publicFavoritesActions.CHECK_PRESENCE_IN_PUBLIC_FAVORITES(resourceUuids));
         services.favoriteService
index 1b7173fde3da291a4277a36e97f13429bcc40570..7e69a72873daa26e9ce1dc26d2154906f96fdb0c 100644 (file)
@@ -9,7 +9,6 @@ import { History } from "history";
 
 import { authReducer } from "./auth/auth-reducer";
 import { authMiddleware } from "./auth/auth-middleware";
-import { configReducer } from "./config/config-reducer";
 import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
 import { detailsPanelReducer } from './details-panel/details-panel-reducer';
 import { contextMenuReducer } from './context-menu/context-menu-reducer';
@@ -148,7 +147,6 @@ export function configureStore(history: History, services: ServiceRepository): R
 
 const createRootReducer = (services: ServiceRepository) => combineReducers({
     auth: authReducer(services),
-    config: configReducer,
     collectionPanel: collectionPanelReducer,
     collectionPanelFiles: collectionPanelFilesReducer,
     contextMenu: contextMenuReducer,
index 3fc718fff3d4e9660cb5e7adfeca2cccb7d4a93f..7229bf63381d0f3935b6a56ea52beeed773e478d 100644 (file)
@@ -264,7 +264,7 @@ export const loadFavoritesProject = (params: LoadFavoritesProjectParams) =>
 export const loadPublicFavoritesProject = (params: LoadFavoritesProjectParams) =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         const { pickerId, includeCollections = false, includeFiles = false } = params;
-        const uuidPrefix = getState().config.uuidPrefix;
+        const uuidPrefix = getState().auth.config.uuidPrefix;
         const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
         if (uuid) {
 
index 8d7d0dcd7dc049fc5bafe6aa6ac13c68127a7b7a..26c942d6a4d4f7423e11c6a7bd8800ae52bc7663 100644 (file)
@@ -56,7 +56,7 @@ export const loginAs = (uuid: string) =>
         const data = getResource<UserResource>(uuid)(resources);
         const client = await services.apiClientAuthorizationService.create({ ownerUuid: uuid });
         if (data) {
-            dispatch<any>(authActions.INIT({ user: data, token: `v2/${client.uuid}/${client.apiToken}` }));
+            dispatch<any>(authActions.INIT_USER({ user: data, token: `v2/${client.uuid}/${client.apiToken}` }));
             location.reload();
             dispatch<any>(navigateToRootProject);
         }
index 722180534469b1eebcc779b40d491408251ac4d7..346a9ef02ea529db2fa33d4e5664ed7a567ddf40 100644 (file)
@@ -32,7 +32,7 @@ interface AccountMenuProps {
 const mapStateToProps = (state: RootState): AccountMenuProps => ({
     user: state.auth.user,
     currentRoute: state.router.location ? state.router.location.pathname : '',
-    workbenchURL: state.config.workbenchUrl,
+    workbenchURL: state.auth.config.workbenchUrl,
     apiToken: state.auth.apiToken,
     localCluster: state.auth.localCluster
 });
index 91b4a51df8c41b150613077c277d7406b3226f7a..42262deb06beb67da2e3caa550300e3e026ef7c5 100644 (file)
@@ -54,7 +54,7 @@ export interface InactivePanelStateProps {
 type InactivePanelProps = WithStyles<CssRules> & InactivePanelActionProps & InactivePanelStateProps;
 
 export const InactivePanel = connect((state: RootState) => ({
-    inactivePageText: state.config.clusterConfig.Workbench.InactivePageHTML
+    inactivePageText: state.auth.config.clusterConfig.Workbench.InactivePageHTML
 }), mapDispatchToProps)(withStyles(styles)((({ classes, startLinking, inactivePageText }: InactivePanelProps) =>
     <Grid container justify="center" alignItems="center" direction="column" spacing={24}
         className={classes.root}
index 293026c3d6733f19ad86f84b00006d8c143b03f9..6fe3eee2aeb6a5425615ae9dec713ec6d4b57bb7 100644 (file)
@@ -61,7 +61,7 @@ export const LoginPanel = withStyles(styles)(
         homeCluster: state.auth.homeCluster,
         uuidPrefix: state.auth.localCluster,
         loginCluster: state.auth.loginCluster,
-        welcomePage: state.config.clusterConfig.Workbench.WelcomePageHTML
+        welcomePage: state.auth.config.clusterConfig.Workbench.WelcomePageHTML
     }))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix, loginCluster, welcomePage }: LoginPanelProps) =>
         <Grid container justify="center" alignItems="center"
             className={classes.root}
index dab4533fa306107b89bd6f28aabb22565e826c6b..5828c6db9054e8fc980a7029b174f7f6816c5893 100644 (file)
@@ -19,7 +19,7 @@ const mapStateToProps = (state: RootState): MainPanelRootDataProps => {
         uuidPrefix: state.auth.localCluster,
         isNotLinking: state.linkAccountPanel.status === LinkAccountPanelStatus.NONE || state.linkAccountPanel.status === LinkAccountPanelStatus.INITIAL,
         isLinkingPath: state.router.location ? matchLinkAccountRoute(state.router.location.pathname) !== null : false,
-        siteBanner: state.config.clusterConfig.Workbench.SiteName
+        siteBanner: state.auth.config.clusterConfig.Workbench.SiteName
     };
 };