[ARVADOS-WORKBENCH2] created: 1.1.4-437-gf239b9e
Git user
git at public.curoverse.com
Sun Jul 29 18:47:49 EDT 2018
at f239b9e88677d82a48b6b565ab2fd407d1171729 (commit)
commit f239b9e88677d82a48b6b565ab2fd407d1171729
Author: Daniel Kos <daniel.kos at contractors.roche.com>
Date: Mon Jul 30 00:47:41 2018 +0200
Introduce service repository
Feature #13901
Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos at contractors.roche.com>
diff --git a/src/index.tsx b/src/index.tsx
index 77d5763..4b2e335 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -13,7 +13,7 @@ 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 } from "./services/services";
+import { createServices } from "./services/services";
import { getProjectList } from "./store/project/project-action";
import { MuiThemeProvider } from '@material-ui/core/styles';
import { CustomTheme } from './common/custom-theme';
@@ -36,10 +36,13 @@ fetchConfig()
setBaseUrl(config.API_HOST);
const history = createBrowserHistory();
- const store = configureStore(history);
+ const services = createServices();
+ const store = configureStore(history, services);
store.dispatch(authActions.INIT());
- store.dispatch<any>(getProjectList(authService.getUuid()));
+ store.dispatch<any>(getProjectList(services.authService.getUuid()));
+
+ const Token = (props: any) => <ApiToken authService={services.authService} {...props}/>;
const App = () =>
<MuiThemeProvider theme={CustomTheme}>
@@ -47,7 +50,7 @@ fetchConfig()
<ConnectedRouter history={history}>
<div>
<Route path="/" component={Workbench} />
- <Route path="/token" component={ApiToken} />
+ <Route path="/token" component={Token} />
</div>
</ConnectedRouter>
</Provider>
diff --git a/src/services/services.ts b/src/services/services.ts
index a08ed3c..55ab836 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -9,8 +9,26 @@ import { ProjectService } from "./project-service/project-service";
import { LinkService } from "./link-service/link-service";
import { FavoriteService } from "./favorite-service/favorite-service";
-export const authService = new AuthService(authClient, apiClient);
-export const groupsService = new GroupsService(apiClient);
-export const projectService = new ProjectService(apiClient);
-export const linkService = new LinkService(apiClient);
-export const favoriteService = new FavoriteService(linkService, groupsService);
+export interface ServiceRepository {
+ authService: AuthService;
+ groupsService: GroupsService;
+ projectService: ProjectService;
+ linkService: LinkService;
+ favoriteService: FavoriteService;
+}
+
+export const createServices = (): ServiceRepository => {
+ const authService = new AuthService(authClient, apiClient);
+ const groupsService = new GroupsService(apiClient);
+ const projectService = new ProjectService(apiClient);
+ const linkService = new LinkService(apiClient);
+ const favoriteService = new FavoriteService(linkService, groupsService);
+
+ return {
+ authService,
+ groupsService,
+ projectService,
+ linkService,
+ favoriteService
+ };
+};
diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index e9930a0..8b268cc 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -4,8 +4,9 @@
import { ofType, default as unionize, UnionOf } from "unionize";
import { Dispatch } from "redux";
-import { authService } from "../../services/services";
import { User } from "../../models/user";
+import { RootState } from "../store";
+import { ServiceRepository } from "../../services/services";
export const authActions = unionize({
SAVE_API_TOKEN: ofType<string>(),
@@ -19,9 +20,9 @@ export const authActions = unionize({
value: 'payload'
});
-export const getUserDetails = () => (dispatch: Dispatch): Promise<User> => {
+export const getUserDetails = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<User> => {
dispatch(authActions.USER_DETAILS_REQUEST());
- return authService.getUserDetails().then(details => {
+ return services.authService.getUserDetails().then(details => {
dispatch(authActions.USER_DETAILS_SUCCESS(details));
return details;
});
diff --git a/src/store/auth/auth-reducer.test.ts b/src/store/auth/auth-reducer.test.ts
index 778b500..f686db5 100644
--- a/src/store/auth/auth-reducer.test.ts
+++ b/src/store/auth/auth-reducer.test.ts
@@ -2,8 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { authReducer } from "./auth-reducer";
-import { authActions } from "./auth-action";
+import { authReducer, AuthState } from "./auth-reducer";
+import { AuthAction, authActions } from "./auth-action";
import {
API_TOKEN_KEY,
USER_EMAIL_KEY,
@@ -14,15 +14,19 @@ import {
} from "../../services/auth-service/auth-service";
import 'jest-localstorage-mock';
+import { createServices } from "../../services/services";
describe('auth-reducer', () => {
+ let reducer: (state: AuthState | undefined, action: AuthAction) => any;
+
beforeAll(() => {
localStorage.clear();
+ reducer = authReducer(createServices());
});
it('should return default state on initialisation', () => {
const initialState = undefined;
- const state = authReducer(initialState, authActions.INIT());
+ const state = reducer(initialState, authActions.INIT());
expect(state).toEqual({
apiToken: undefined,
user: undefined
@@ -39,7 +43,7 @@ describe('auth-reducer', () => {
localStorage.setItem(USER_UUID_KEY, "uuid");
localStorage.setItem(USER_OWNER_UUID_KEY, "ownerUuid");
- const state = authReducer(initialState, authActions.INIT());
+ const state = reducer(initialState, authActions.INIT());
expect(state).toEqual({
apiToken: "token",
user: {
@@ -55,7 +59,7 @@ describe('auth-reducer', () => {
it('should store token in local storage', () => {
const initialState = undefined;
- const state = authReducer(initialState, authActions.SAVE_API_TOKEN("token"));
+ const state = reducer(initialState, authActions.SAVE_API_TOKEN("token"));
expect(state).toEqual({
apiToken: "token",
user: undefined
@@ -75,7 +79,7 @@ describe('auth-reducer', () => {
ownerUuid: "ownerUuid"
};
- const state = authReducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
+ const state = reducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
expect(state).toEqual({
apiToken: undefined,
user: {
@@ -93,7 +97,7 @@ describe('auth-reducer', () => {
it('should fire external url to login', () => {
const initialState = undefined;
window.location.assign = jest.fn();
- authReducer(initialState, authActions.LOGIN());
+ reducer(initialState, authActions.LOGIN());
expect(window.location.assign).toBeCalledWith(
`/login?return_to=${window.location.protocol}//${window.location.host}/token`
);
@@ -102,7 +106,7 @@ describe('auth-reducer', () => {
it('should fire external url to logout', () => {
const initialState = undefined;
window.location.assign = jest.fn();
- authReducer(initialState, authActions.LOGOUT());
+ reducer(initialState, authActions.LOGOUT());
expect(window.location.assign).toBeCalledWith(
`/logout?return_to=${location.protocol}//${location.host}`
);
diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts
index 366385d..e3f968a 100644
--- a/src/store/auth/auth-reducer.ts
+++ b/src/store/auth/auth-reducer.ts
@@ -4,7 +4,7 @@
import { authActions, AuthAction } from "./auth-action";
import { User } from "../../models/user";
-import { authService } from "../../services/services";
+import { ServiceRepository } from "../../services/services";
import { removeServerApiAuthorizationHeader, setServerApiAuthorizationHeader } from "../../common/api/server-api";
export interface AuthState {
@@ -12,34 +12,34 @@ export interface AuthState {
apiToken?: string;
}
-export const authReducer = (state: AuthState = {}, action: AuthAction) => {
+export const authReducer = (services: ServiceRepository) => (state: AuthState = {}, action: AuthAction) => {
return authActions.match(action, {
SAVE_API_TOKEN: (token: string) => {
- authService.saveApiToken(token);
+ services.authService.saveApiToken(token);
setServerApiAuthorizationHeader(token);
return {...state, apiToken: token};
},
INIT: () => {
- const user = authService.getUser();
- const token = authService.getApiToken();
+ const user = services.authService.getUser();
+ const token = services.authService.getApiToken();
if (token) {
setServerApiAuthorizationHeader(token);
}
return {user, apiToken: token};
},
LOGIN: () => {
- authService.login();
+ services.authService.login();
return state;
},
LOGOUT: () => {
- authService.removeApiToken();
- authService.removeUser();
+ services.authService.removeApiToken();
+ services.authService.removeUser();
removeServerApiAuthorizationHeader();
- authService.logout();
+ services.authService.logout();
return {...state, apiToken: undefined};
},
USER_DETAILS_SUCCESS: (user: User) => {
- authService.saveUser(user);
+ services.authService.saveUser(user);
return {...state, user};
},
default: () => state
diff --git a/src/store/favorite-panel/favorite-panel-middleware.ts b/src/store/favorite-panel/favorite-panel-middleware.ts
index 548a117..62e93c6 100644
--- a/src/store/favorite-panel/favorite-panel-middleware.ts
+++ b/src/store/favorite-panel/favorite-panel-middleware.ts
@@ -4,7 +4,6 @@
import { Middleware } from "redux";
import { dataExplorerActions } from "../data-explorer/data-explorer-action";
-import { favoriteService } from "../../services/services";
import { RootState } from "../store";
import { getDataExplorer } from "../data-explorer/data-explorer-reducer";
import { FilterBuilder } from "../../common/api/filter-builder";
@@ -22,8 +21,9 @@ import { OrderBuilder } from "../../common/api/order-builder";
import { SortDirection } from "../../components/data-table/data-column";
import { GroupContentsResource, GroupContentsResourcePrefix } from "../../services/groups-service/groups-service";
import { FavoriteOrderBuilder } from "../../services/favorite-service/favorite-order-builder";
+import { ServiceRepository } from "../../services/services";
-export const favoritePanelMiddleware: Middleware = store => next => {
+export const favoritePanelMiddleware = (services: ServiceRepository): Middleware => store => next => {
next(dataExplorerActions.SET_COLUMNS({ id: FAVORITE_PANEL_ID, columns }));
return action => {
@@ -62,7 +62,7 @@ export const favoritePanelMiddleware: Middleware = store => next => {
const typeFilters = getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
const order = FavoriteOrderBuilder.create();
if (typeFilters.length > 0) {
- favoriteService
+ services.favoriteService
.list(state.projects.currentItemId, {
limit: dataExplorer.rowsPerPage,
offset: dataExplorer.page * dataExplorer.rowsPerPage,
diff --git a/src/store/favorites/favorites-actions.ts b/src/store/favorites/favorites-actions.ts
index eb4f649..38229df 100644
--- a/src/store/favorites/favorites-actions.ts
+++ b/src/store/favorites/favorites-actions.ts
@@ -4,10 +4,10 @@
import { unionize, ofType, UnionOf } from "unionize";
import { Dispatch } from "redux";
-import { favoriteService } from "../../services/services";
import { RootState } from "../store";
import { checkFavorite } from "./favorites-reducer";
import { snackbarActions } from "../snackbar/snackbar-actions";
+import { ServiceRepository } from "../../services/services";
export const favoritesActions = unionize({
TOGGLE_FAVORITE: ofType<{ resourceUuid: string }>(),
@@ -18,14 +18,14 @@ export const favoritesActions = unionize({
export type FavoritesAction = UnionOf<typeof favoritesActions>;
export const toggleFavorite = (resource: { uuid: string; name: string }) =>
- (dispatch: Dispatch, getState: () => RootState): Promise<any> => {
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
const userUuid = getState().auth.user!.uuid;
dispatch(favoritesActions.TOGGLE_FAVORITE({ resourceUuid: resource.uuid }));
dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Working..." }));
const isFavorite = checkFavorite(resource.uuid, getState().favorites);
const promise: any = isFavorite
- ? favoriteService.delete({ userUuid, resourceUuid: resource.uuid })
- : favoriteService.create({ userUuid, resource });
+ ? services.favoriteService.delete({ userUuid, resourceUuid: resource.uuid })
+ : services.favoriteService.create({ userUuid, resource });
return promise
.then(() => {
@@ -41,12 +41,12 @@ export const toggleFavorite = (resource: { uuid: string; name: string }) =>
};
export const checkPresenceInFavorites = (resourceUuids: string[]) =>
- (dispatch: Dispatch, getState: () => RootState) => {
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const userUuid = getState().auth.user!.uuid;
dispatch(favoritesActions.CHECK_PRESENCE_IN_FAVORITES(resourceUuids));
- favoriteService
+ services.favoriteService
.checkPresenceInFavorites(userUuid, resourceUuids)
- .then(results => {
+ .then((results: any) => {
dispatch(favoritesActions.UPDATE_FAVORITES(results));
});
};
diff --git a/src/store/project-panel/project-panel-middleware.ts b/src/store/project-panel/project-panel-middleware.ts
index b7ab03c..e984e06 100644
--- a/src/store/project-panel/project-panel-middleware.ts
+++ b/src/store/project-panel/project-panel-middleware.ts
@@ -5,7 +5,6 @@
import { Middleware } from "redux";
import { dataExplorerActions } from "../data-explorer/data-explorer-action";
import { PROJECT_PANEL_ID, columns, ProjectPanelFilter, ProjectPanelColumnNames } from "../../views/project-panel/project-panel";
-import { groupsService } from "../../services/services";
import { RootState } from "../store";
import { getDataExplorer } from "../data-explorer/data-explorer-reducer";
import { resourceToDataItem, ProjectPanelItem } from "../../views/project-panel/project-panel-item";
@@ -16,8 +15,9 @@ import { OrderBuilder } from "../../common/api/order-builder";
import { GroupContentsResource, GroupContentsResourcePrefix } from "../../services/groups-service/groups-service";
import { SortDirection } from "../../components/data-table/data-column";
import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+import { ServiceRepository } from "../../services/services";
-export const projectPanelMiddleware: Middleware = store => next => {
+export const projectPanelMiddleware = (services: ServiceRepository): Middleware => store => next => {
next(dataExplorerActions.SET_COLUMNS({ id: PROJECT_PANEL_ID, columns }));
return action => {
@@ -57,7 +57,7 @@ export const projectPanelMiddleware: Middleware = store => next => {
const sortColumn = dataExplorer.columns.find(({ sortDirection }) => Boolean(sortDirection && sortDirection !== "none"));
const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC ? SortDirection.ASC : SortDirection.DESC;
if (typeFilters.length > 0) {
- groupsService
+ services.groupsService
.contents(state.projects.currentItemId, {
limit: dataExplorer.rowsPerPage,
offset: dataExplorer.page * dataExplorer.rowsPerPage,
diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts
index cf38456..2f9963b 100644
--- a/src/store/project/project-action.ts
+++ b/src/store/project/project-action.ts
@@ -4,11 +4,11 @@
import { default as unionize, ofType, UnionOf } from "unionize";
import { ProjectResource } from "../../models/project";
-import { projectService } from "../../services/services";
import { Dispatch } from "redux";
import { FilterBuilder } from "../../common/api/filter-builder";
import { RootState } from "../store";
import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+import { ServiceRepository } from "../../services/services";
export const projectActions = unionize({
OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(),
@@ -26,9 +26,9 @@ export const projectActions = unionize({
value: 'payload'
});
-export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch, getState: () => RootState) => {
+export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
- return projectService.list({
+ return services.projectService.list({
filters: FilterBuilder
.create<ProjectResource>()
.addEqual("ownerUuid", parentUuid)
@@ -40,11 +40,11 @@ export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch,
};
export const createProject = (project: Partial<ProjectResource>) =>
- (dispatch: Dispatch, getState: () => RootState) => {
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const { ownerUuid } = getState().projects.creator;
const projectData = { ownerUuid, ...project };
dispatch(projectActions.CREATE_PROJECT(projectData));
- return projectService
+ return services.projectService
.create(projectData)
.then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project)));
};
diff --git a/src/store/store.ts b/src/store/store.ts
index ae07744..cf07d6d 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -18,6 +18,7 @@ import { favoritePanelMiddleware } from "./favorite-panel/favorite-panel-middlew
import { reducer as formReducer } from 'redux-form';
import { FavoritesState, favoritesReducer } from './favorites/favorites-reducer';
import { snackbarReducer, SnackbarState } from './snackbar/snackbar-reducer';
+import { ServiceRepository } from "../services/services";
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
@@ -36,26 +37,25 @@ export interface RootState {
snackbar: SnackbarState;
}
-const rootReducer = combineReducers({
- auth: authReducer,
- projects: projectsReducer,
- router: routerReducer,
- dataExplorer: dataExplorerReducer,
- sidePanel: sidePanelReducer,
- detailsPanel: detailsPanelReducer,
- contextMenu: contextMenuReducer,
- form: formReducer,
- favorites: favoritesReducer,
- snackbar: snackbarReducer,
-});
+export function configureStore(history: History, services: ServiceRepository) {
+ const rootReducer = combineReducers({
+ auth: authReducer(services),
+ projects: projectsReducer,
+ router: routerReducer,
+ dataExplorer: dataExplorerReducer,
+ sidePanel: sidePanelReducer,
+ detailsPanel: detailsPanelReducer,
+ contextMenu: contextMenuReducer,
+ form: formReducer,
+ favorites: favoritesReducer,
+ snackbar: snackbarReducer,
+ });
-
-export function configureStore(history: History) {
const middlewares: Middleware[] = [
routerMiddleware(history),
- thunkMiddleware,
- projectPanelMiddleware,
- favoritePanelMiddleware
+ thunkMiddleware.withExtraArgument(services),
+ projectPanelMiddleware(services),
+ favoritePanelMiddleware(services)
];
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
return createStore(rootReducer, enhancer);
diff --git a/src/views-components/api-token/api-token.tsx b/src/views-components/api-token/api-token.tsx
index 1d017cc..51e3ad1 100644
--- a/src/views-components/api-token/api-token.tsx
+++ b/src/views-components/api-token/api-token.tsx
@@ -6,11 +6,12 @@ import { Redirect, RouteProps } from "react-router";
import * as React from "react";
import { connect, DispatchProp } from "react-redux";
import { authActions, getUserDetails } from "../../store/auth/auth-action";
-import { authService } from "../../services/services";
import { getProjectList } from "../../store/project/project-action";
import { getUrlParameter } from "../../common/url";
+import { AuthService } from "../../services/auth-service/auth-service";
interface ApiTokenProps {
+ authService: AuthService;
}
export const ApiToken = connect()(
@@ -20,7 +21,7 @@ export const ApiToken = connect()(
const apiToken = getUrlParameter(search, 'api_token');
this.props.dispatch(authActions.SAVE_API_TOKEN(apiToken));
this.props.dispatch<any>(getUserDetails()).then(() => {
- const rootUuid = authService.getRootUuid();
+ const rootUuid = this.props.authService.getRootUuid();
this.props.dispatch(getProjectList(rootUuid));
});
}
diff --git a/src/views/workbench/workbench.test.tsx b/src/views/workbench/workbench.test.tsx
index 538b8e7..48ea2de 100644
--- a/src/views/workbench/workbench.test.tsx
+++ b/src/views/workbench/workbench.test.tsx
@@ -11,12 +11,13 @@ import createBrowserHistory from "history/createBrowserHistory";
import { ConnectedRouter } from "react-router-redux";
import { MuiThemeProvider } from '@material-ui/core/styles';
import { CustomTheme } from '../../common/custom-theme';
+import { createServices } from "../../services/services";
const history = createBrowserHistory();
it('renders without crashing', () => {
const div = document.createElement('div');
- const store = configureStore(createBrowserHistory());
+ const store = configureStore(createBrowserHistory(), createServices());
ReactDOM.render(
<MuiThemeProvider theme={CustomTheme}>
<Provider store={store}>
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 3637528..b0fe4b3 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -24,7 +24,6 @@ import { ProjectPanel } from "../project-panel/project-panel";
import { DetailsPanel } from '../../views-components/details-panel/details-panel';
import { ArvadosTheme } from '../../common/custom-theme';
import { CreateProjectDialog } from "../../views-components/create-project-dialog/create-project-dialog";
-import { authService } from '../../services/services';
import { detailsPanelActions, loadDetails } from "../../store/details-panel/details-panel-action";
import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
@@ -36,6 +35,7 @@ import { FavoritePanel, FAVORITE_PANEL_ID } from "../favorite-panel/favorite-pan
import { CurrentTokenDialog } from '../../views-components/current-token-dialog/current-token-dialog';
import { dataExplorerActions } from '../../store/data-explorer/data-explorer-action';
import { Snackbar } from '../../views-components/snackbar/snackbar';
+import { AuthService } from "../../services/auth-service/auth-service";
const drawerWidth = 240;
const appBarHeight = 100;
@@ -86,10 +86,14 @@ interface WorkbenchDataProps {
sidePanelItems: SidePanelItem[];
}
+interface WorkbenchServiceProps {
+ authService: AuthService;
+}
+
interface WorkbenchActionProps {
}
-type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
+type WorkbenchProps = WorkbenchDataProps & WorkbenchServiceProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
interface NavBreadcrumb extends Breadcrumb {
itemId: string;
@@ -188,7 +192,7 @@ export const Workbench = withStyles(styles)(
toggleActive={this.toggleSidePanelActive}
sidePanelItems={this.props.sidePanelItems}
onContextMenu={(event) => this.openContextMenu(event, {
- uuid: authService.getUuid() || "",
+ uuid: this.props.authService.getUuid() || "",
name: "",
kind: ContextMenuKind.ROOT_PROJECT
})}>
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list