Decoupled services from redux
authorDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 19 Jun 2018 08:49:17 +0000 (10:49 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 19 Jun 2018 08:49:17 +0000 (10:49 +0200)
Feature #13632

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

src/index.tsx
src/services/auth-service/auth-service.ts
src/services/collection-service/collection-service.ts
src/services/project-service/project-service.ts
src/store/auth/auth-action.ts
src/store/auth/auth-reducer.test.ts
src/store/auth/auth-reducer.ts
src/store/collection/collection-action.ts
src/store/project/project-action.ts
src/views-components/api-token/api-token.tsx
src/views/workbench/workbench.tsx

index 9e1f103c0d2b340591f4165fb43b40f4dd8a7d81..cf1610f83226cd12a279b4a28db1f3494ee740a7 100644 (file)
@@ -6,7 +6,6 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { Provider } from "react-redux";
 import Workbench from './views/workbench/workbench';
-import ProjectList from './views-components/project-list/project-list';
 import './index.css';
 import { Route } from "react-router";
 import createBrowserHistory from "history/createBrowserHistory";
@@ -14,7 +13,8 @@ import configureStore from "./store/store";
 import { ConnectedRouter } from "react-router-redux";
 import ApiToken from "./views-components/api-token/api-token";
 import authActions from "./store/auth/auth-action";
-import { authService, projectService } from "./services/services";
+import { authService } from "./services/services";
+import { getProjectList } from "./store/project/project-action";
 
 const history = createBrowserHistory();
 
@@ -31,7 +31,7 @@ const store = configureStore({
 
 store.dispatch(authActions.INIT());
 const rootUuid = authService.getRootUuid();
-store.dispatch<any>(projectService.getProjectList(rootUuid));
+store.dispatch<any>(getProjectList(rootUuid));
 
 const App = () =>
     <Provider store={store}>
index 5878dc6ed5f01e9dc5657d122d41239fc005554d..d71f0299aa6cd866d30da96dd59fd69e17b94f03 100644 (file)
@@ -4,8 +4,6 @@
 
 import { API_HOST, serverApi } from "../../common/api/server-api";
 import { User } from "../../models/user";
-import { Dispatch } from "redux";
-import actions from "../../store/auth/auth-action";
 
 export const API_TOKEN_KEY = 'apiToken';
 export const USER_EMAIL_KEY = 'userEmail';
@@ -79,13 +77,16 @@ export default class AuthService {
         window.location.assign(`${API_HOST}/logout?return_to=${currentUrl}`);
     }
 
-    public getUserDetails = () => (dispatch: Dispatch): Promise<void> => {
-        dispatch(actions.USER_DETAILS_REQUEST());
+    public getUserDetails = (): Promise<User> => {
         return serverApi
             .get<UserDetailsResponse>('/users/current')
-            .then(resp => {
-                dispatch(actions.USER_DETAILS_SUCCESS(resp.data));
-            });
+            .then(resp => ({
+                email: resp.data.email,
+                firstName: resp.data.first_name,
+                lastName: resp.data.last_name,
+                uuid: resp.data.uuid,
+                ownerUuid: resp.data.owner_uuid
+            }));
     }
 
     public getRootUuid() {
index 77ea7ea311d9f5839a26f713e3d0a119129e8bf5..171cd8565ffc503a2137f56476997356fc1e4b11 100644 (file)
@@ -3,9 +3,6 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { serverApi } from "../../common/api/server-api";
-import { Dispatch } from "redux";
-import actions from "../../store/collection/collection-action";
-import UrlBuilder from "../../common/api/url-builder";
 import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
 import { ArvadosResource } from "../response";
 import { Collection } from "../../models/collection";
@@ -31,8 +28,7 @@ interface CollectionsResponse {
 }
 
 export default class CollectionService {
-    public getCollectionList = (parentUuid?: string) => (dispatch: Dispatch): Promise<Collection[]> => {
-        dispatch(actions.COLLECTIONS_REQUEST());
+    public getCollectionList = (parentUuid?: string): Promise<Collection[]> => {
         if (parentUuid) {
             const fb = new FilterBuilder();
             fb.addLike(FilterField.OWNER_UUID, parentUuid);
@@ -48,11 +44,9 @@ export default class CollectionService {
                     ownerUuid: g.owner_uuid,
                     kind: g.kind
                 } as Collection));
-                dispatch(actions.COLLECTIONS_SUCCESS({collections}));
                 return collections;
             });
         } else {
-            dispatch(actions.COLLECTIONS_SUCCESS({collections: []}));
             return Promise.resolve([]);
         }
     }
index 8070a386d1bf7d1226ee1fb5a2c4687b162284e6..bc34081811fdbbdd6aaa14437087ab82549a08fa 100644 (file)
@@ -4,9 +4,7 @@
 
 import { serverApi } from "../../common/api/server-api";
 import { Dispatch } from "redux";
-import actions from "../../store/project/project-action";
 import { Project } from "../../models/project";
-import UrlBuilder from "../../common/api/url-builder";
 import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
 import { ArvadosResource } from "../response";
 
@@ -27,8 +25,7 @@ interface GroupsResponse {
 }
 
 export default class ProjectService {
-    public getProjectList = (parentUuid?: string) => (dispatch: Dispatch): Promise<Project[]> => {
-        dispatch(actions.PROJECTS_REQUEST(parentUuid));
+    public getProjectList = (parentUuid?: string): Promise<Project[]> => {
         if (parentUuid) {
             const fb = new FilterBuilder();
             fb.addLike(FilterField.OWNER_UUID, parentUuid);
@@ -44,11 +41,9 @@ export default class ProjectService {
                     ownerUuid: g.owner_uuid,
                     kind: g.kind
                 } as Project));
-                dispatch(actions.PROJECTS_SUCCESS({projects, parentItemId: parentUuid}));
                 return projects;
             });
         } else {
-            dispatch(actions.PROJECTS_SUCCESS({projects: [], parentItemId: parentUuid}));
             return Promise.resolve([]);
         }
     }
index e18c78b106f8578b2d396127c74f486f3d62d2ff..a6e6f79794db27bc64178b6f53626c6e688c3865 100644 (file)
@@ -3,7 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ofType, default as unionize, UnionOf } from "unionize";
-import { UserDetailsResponse } from "../../services/auth-service/auth-service";
+import { Dispatch } from "redux";
+import { authService } from "../../services/services";
+import { User } from "../../models/user";
 
 const actions = unionize({
     SAVE_API_TOKEN: ofType<string>(),
@@ -11,11 +13,20 @@ const actions = unionize({
     LOGOUT: {},
     INIT: {},
     USER_DETAILS_REQUEST: {},
-    USER_DETAILS_SUCCESS: ofType<UserDetailsResponse>()
+    USER_DETAILS_SUCCESS: ofType<User>()
 }, {
     tag: 'type',
     value: 'payload'
 });
 
+export const getUserDetails = () => (dispatch: Dispatch): Promise<User> => {
+    dispatch(actions.USER_DETAILS_REQUEST());
+    return authService.getUserDetails().then(details => {
+        dispatch(actions.USER_DETAILS_SUCCESS(details));
+        return details;
+    });
+};
+
+
 export type AuthAction = UnionOf<typeof actions>;
 export default actions;
index a60e82a6c8bde1d4f326ba341dd5f908119a49ff..2e7c1a248800f94cf5bfdbe03c07e9e2f980d093 100644 (file)
@@ -68,16 +68,15 @@ describe('auth-reducer', () => {
     it('should set user details on success fetch', () => {
         const initialState = undefined;
 
-        const userDetails = {
+        const user = {
             email: "test@test.com",
-            first_name: "John",
-            last_name: "Doe",
+            firstName: "John",
+            lastName: "Doe",
             uuid: "uuid",
-            owner_uuid: "ownerUuid",
-            is_admin: true
+            ownerUuid: "ownerUuid"
         };
 
-        const state = authReducer(initialState, actions.USER_DETAILS_SUCCESS(userDetails));
+        const state = authReducer(initialState, actions.USER_DETAILS_SUCCESS(user));
         expect(state).toEqual({
             apiToken: undefined,
             user: {
index 02b9d30c30dcf422cb7ab631186654de76f6ccdc..f6974fd2073be49a5c01fe6677bff22edbc4ae8d 100644 (file)
@@ -6,7 +6,6 @@ import actions, { AuthAction } from "./auth-action";
 import { User } from "../../models/user";
 import { authService } from "../../services/services";
 import { removeServerApiAuthorizationHeader, setServerApiAuthorizationHeader } from "../../common/api/server-api";
-import { UserDetailsResponse } from "../../services/auth-service/auth-service";
 
 export interface AuthState {
     user?: User;
@@ -39,14 +38,7 @@ const authReducer = (state: AuthState = {}, action: AuthAction) => {
             authService.logout();
             return {...state, apiToken: undefined};
         },
-        USER_DETAILS_SUCCESS: (ud: UserDetailsResponse) => {
-            const user = {
-                email: ud.email,
-                firstName: ud.first_name,
-                lastName: ud.last_name,
-                uuid: ud.uuid,
-                ownerUuid: ud.owner_uuid
-            };
+        USER_DETAILS_SUCCESS: (user: User) => {
             authService.saveUser(user);
             return {...state, user};
         },
index 5f1d60f3927e706b10c03dd7cfa836f32556ff7c..f50e6458ef4ec0549c005ef736ace716480ac652 100644 (file)
@@ -4,6 +4,8 @@
 
 import { Collection } from "../../models/collection";
 import { default as unionize, ofType, UnionOf } from "unionize";
+import { Dispatch } from "redux";
+import { collectionService } from "../../services/services";
 
 const actions = unionize({
     CREATE_COLLECTION: ofType<Collection>(),
@@ -15,5 +17,13 @@ const actions = unionize({
     value: 'payload'
 });
 
+export const getCollectionList = (parentUuid?: string) => (dispatch: Dispatch): Promise<Collection[]> => {
+    dispatch(actions.COLLECTIONS_REQUEST());
+    return collectionService.getCollectionList(parentUuid).then(collections => {
+        dispatch(actions.COLLECTIONS_SUCCESS({collections}));
+        return collections;
+    });
+};
+
 export type CollectionAction = UnionOf<typeof actions>;
 export default actions;
index 2856de66a09a675d122fdd8ea44ed39216d717a0..728b1cc95e587112104d032f6cbc56698fe45426 100644 (file)
@@ -4,6 +4,8 @@
 
 import { Project } from "../../models/project";
 import { default as unionize, ofType, UnionOf } from "unionize";
+import { projectService } from "../../services/services";
+import { Dispatch } from "redux";
 
 const actions = unionize({
     CREATE_PROJECT: ofType<Project>(),
@@ -16,5 +18,13 @@ const actions = unionize({
     value: 'payload'
 });
 
+export const getProjectList = (parentUuid?: string) => (dispatch: Dispatch): Promise<Project[]> => {
+    dispatch(actions.PROJECTS_REQUEST());
+    return projectService.getProjectList(parentUuid).then(projects => {
+        dispatch(actions.PROJECTS_SUCCESS({projects, parentItemId: parentUuid}));
+        return projects;
+    });
+};
+
 export type ProjectAction = UnionOf<typeof actions>;
 export default actions;
index 7656bf873368308f93947d943342d8de3c1471d5..e4ba4914a3518e95c557f3f93cc28293717dbd63 100644 (file)
@@ -5,8 +5,9 @@
 import { Redirect, RouteProps } from "react-router";
 import * as React from "react";
 import { connect, DispatchProp } from "react-redux";
-import authActions from "../../store/auth/auth-action";
-import { authService, projectService } from "../../services/services";
+import authActions, { getUserDetails } from "../../store/auth/auth-action";
+import { authService } from "../../services/services";
+import { getProjectList } from "../../store/project/project-action";
 
 interface ApiTokenProps {
 }
@@ -23,9 +24,9 @@ class ApiToken extends React.Component<ApiTokenProps & RouteProps & DispatchProp
         const search = this.props.location ? this.props.location.search : "";
         const apiToken = ApiToken.getUrlParameter(search, 'api_token');
         this.props.dispatch(authActions.SAVE_API_TOKEN(apiToken));
-        this.props.dispatch<any>(authService.getUserDetails()).then(() => {
+        this.props.dispatch<any>(getUserDetails()).then(() => {
             const rootUuid = authService.getRootUuid();
-            this.props.dispatch(projectService.getProjectList(rootUuid));
+            this.props.dispatch(getProjectList(rootUuid));
         });
     }
     render() {
index 5da3968da90149640cc5b047eee727717743e901..bf05dadc95b98277b25d1bfee28910d5ec10dc2e 100644 (file)
@@ -14,11 +14,10 @@ import { RootState } from "../../store/store";
 import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
 import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
 import { push } from 'react-router-redux';
-import projectActions from "../../store/project/project-action";
+import projectActions, { getProjectList } from "../../store/project/project-action";
 import ProjectTree from '../../views-components/project-tree/project-tree';
 import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
 import { Project } from "../../models/project";
-import { projectService } from '../../services/services';
 import { getTreePath } from '../../store/project/project-reducer';
 import DataExplorer from '../data-explorer/data-explorer';
 
@@ -133,7 +132,7 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
         if (status === TreeItemStatus.Loaded) {
             this.openProjectItem(itemId);
         } else {
-            this.props.dispatch<any>(projectService.getProjectList(itemId))
+            this.props.dispatch<any>(getProjectList(itemId))
                 .then(() => this.openProjectItem(itemId));
         }
     }