refs #13632 Merge branch '13632-collections-service'
authorDaniel Kos <daniel.kos@contractors.roche.com>
Wed, 20 Jun 2018 06:09:00 +0000 (08:09 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Wed, 20 Jun 2018 06:09:00 +0000 (08:09 +0200)
# Conflicts:
# src/components/data-table/data-table.tsx
# src/views-components/data-explorer/data-explorer.tsx
# src/views/data-explorer/data-explorer.tsx

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

36 files changed:
src/components/column-selector/column-selector.test.tsx [moved from src/components/data-table/column-selector/column-selector.test.tsx with 97% similarity]
src/components/column-selector/column-selector.tsx [moved from src/components/data-table/column-selector/column-selector.tsx with 94% similarity]
src/components/data-table/data-table.tsx
src/components/data-table/index.ts [deleted file]
src/components/dropdown-menu/dropdown-menu.test.tsx [moved from src/components/main-app-bar/dropdown-menu/dropdown-menu.test.tsx with 100% similarity]
src/components/dropdown-menu/dropdown-menu.tsx [moved from src/components/main-app-bar/dropdown-menu/dropdown-menu.tsx with 100% similarity]
src/components/search-bar/search-bar.test.tsx [moved from src/components/main-app-bar/search-bar/search-bar.test.tsx with 100% similarity]
src/components/search-bar/search-bar.tsx [moved from src/components/main-app-bar/search-bar/search-bar.tsx with 100% similarity]
src/index.tsx
src/models/collection.ts [moved from src/components/data-explorer/index.ts with 52% similarity]
src/models/project.ts
src/models/resource.ts [new file with mode: 0644]
src/services/auth-service/auth-service.ts
src/services/collection-service/collection-service.ts [new file with mode: 0644]
src/services/project-service/project-service.ts
src/services/response.ts [new file with mode: 0644]
src/services/services.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 [new file with mode: 0644]
src/store/collection/collection-reducer.test.ts [new file with mode: 0644]
src/store/collection/collection-reducer.ts [new file with mode: 0644]
src/store/project/project-action.ts
src/store/store.ts
src/views-components/api-token/api-token.tsx [moved from src/components/api-token/api-token.tsx with 77% similarity]
src/views-components/data-explorer/data-explorer.tsx [moved from src/components/data-explorer/data-explorer.tsx with 93% similarity]
src/views-components/data-explorer/data-item.ts [moved from src/components/data-explorer/data-item.ts with 92% similarity]
src/views-components/main-app-bar/main-app-bar.test.tsx [moved from src/components/main-app-bar/main-app-bar.test.tsx with 95% similarity]
src/views-components/main-app-bar/main-app-bar.tsx [moved from src/components/main-app-bar/main-app-bar.tsx with 94% similarity]
src/views-components/project-list/project-list.tsx [moved from src/components/project-list/project-list.tsx with 100% similarity]
src/views-components/project-tree/project-tree.test.tsx [moved from src/components/project-tree/project-tree.test.tsx with 98% similarity]
src/views-components/project-tree/project-tree.tsx [moved from src/components/project-tree/project-tree.tsx with 96% similarity]
src/views/data-explorer/data-explorer-selectors.ts [new file with mode: 0644]
src/views/data-explorer/data-explorer.tsx
src/views/workbench/workbench.tsx

similarity index 97%
rename from src/components/data-table/column-selector/column-selector.test.tsx
rename to src/components/column-selector/column-selector.test.tsx
index 26c16a1b93c70bbf0854b15aaed8d041edac52f5..b6c544fb6b0ed880c82a32fc2939bae1f8036665 100644 (file)
@@ -6,7 +6,7 @@ import * as React from "react";
 import { mount, configure } from "enzyme";
 import * as Adapter from "enzyme-adapter-react-16";
 import ColumnSelector, { ColumnSelectorProps, ColumnSelectorTrigger } from "./column-selector";
-import { DataColumn } from "../data-column";
+import { DataColumn } from "../data-table/data-column";
 import { ListItem, Checkbox } from "@material-ui/core";
 
 configure({ adapter: new Adapter() });
@@ -76,4 +76,4 @@ describe("<ColumnSelector />", () => {
         columnsConfigurator.find(ListItem).simulate("click");
         expect(onColumnToggle).toHaveBeenCalledWith(columns[0]);
     });
-});
\ No newline at end of file
+});
similarity index 94%
rename from src/components/data-table/column-selector/column-selector.tsx
rename to src/components/column-selector/column-selector.tsx
index 87d3e8dbd5997fd24b98d9e9ed9c8b63d130c6f2..e2286b00189396eed805d3d588ae73b96547f78a 100644 (file)
@@ -5,8 +5,8 @@
 import * as React from 'react';
 import { WithStyles, StyleRulesCallback, Theme, withStyles, IconButton, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core';
 import MenuIcon from "@material-ui/icons/Menu";
-import { DataColumn, isColumnConfigurable } from '../data-column';
-import Popover from "../../popover/popover";
+import { DataColumn, isColumnConfigurable } from '../data-table/data-column';
+import Popover from "../popover/popover";
 import { IconButtonProps } from '@material-ui/core/IconButton';
 
 export interface ColumnSelectorProps {
index ec260e967b02afbc03797d3e235a412610ff427f..37e0fe15d52fa7d31272cb9c005034f86d1cfae3 100644 (file)
@@ -6,9 +6,11 @@ import * as React from 'react';
 import { Table, TableBody, TableRow, TableCell, TableHead, StyleRulesCallback, Theme, WithStyles, withStyles, Typography } from '@material-ui/core';
 import { DataColumn } from './data-column';
 
+export type DataColumns<T> = Array<DataColumn<T>>;
+
 export interface DataTableProps<T> {
     items: T[];
-    columns: Array<DataColumn<T>>;
+    columns: DataColumns<T>;
     onRowClick?: (event: React.MouseEvent<HTMLTableRowElement>, item: T) => void;
     onRowContextMenu?: (event: React.MouseEvent<HTMLTableRowElement>, item: T) => void;
 }
@@ -48,7 +50,7 @@ class DataTable<T> extends React.Component<DataTableProps<T> & WithStyles<CssRul
                                 </TableRow>
                             )}
                     </TableBody>
-                </Table> : <Typography 
+                </Table> : <Typography
                     className={classes.noItemsInfo}
                     variant="body2"
                     gutterBottom>
diff --git a/src/components/data-table/index.ts b/src/components/data-table/index.ts
deleted file mode 100644 (file)
index f35754b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-export * from "./data-column";
-export * from "./column-selector/column-selector";
-export { default as ColumnSelector } from "./column-selector/column-selector";
-export * from "./data-table";
-export { default as DataTable } from "./data-table";
\ No newline at end of file
index ca92c381f3993ddcdc7719d4450b7c8e7b8456a9..cf1610f83226cd12a279b4a28db1f3494ee740a7 100644 (file)
@@ -6,15 +6,15 @@ 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 './components/project-list/project-list';
 import './index.css';
 import { Route } from "react-router";
 import createBrowserHistory from "history/createBrowserHistory";
 import configureStore from "./store/store";
 import { ConnectedRouter } from "react-router-redux";
-import ApiToken from "./components/api-token/api-token";
+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}>
similarity index 52%
rename from src/components/data-explorer/index.ts
rename to src/models/collection.ts
index bde402d87c943400f88ed6068c0b752a3554638b..316b1fac59f6271ddc4fb2a0e6844669327f8c39 100644 (file)
@@ -2,5 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-export { default as DataExplorer } from "./data-explorer";
-export * from "./data-item";
\ No newline at end of file
+import { Resource } from "./resource";
+
+export interface Collection extends Resource {
+}
index 830621b440d2e1ab1068eaa665ca39a4791d2328..7d29de872974c62e5c7f9ce6fec6c3b591a1d5e6 100644 (file)
@@ -2,12 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-export interface Project {
-    name: string;
-    createdAt: string;
-    modifiedAt: string;
-    uuid: string;
-    ownerUuid: string;
-    href: string;
-    kind: string;
+import { Resource } from "./resource";
+
+export interface Project extends Resource {
 }
diff --git a/src/models/resource.ts b/src/models/resource.ts
new file mode 100644 (file)
index 0000000..39b4e91
--- /dev/null
@@ -0,0 +1,9 @@
+export interface Resource {
+    name: string;
+    createdAt: string;
+    modifiedAt: string;
+    uuid: string;
+    ownerUuid: string;
+    href: string;
+    kind: string;
+}
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() {
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
new file mode 100644 (file)
index 0000000..171cd85
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { serverApi } from "../../common/api/server-api";
+import FilterBuilder, { FilterField } from "../../common/api/filter-builder";
+import { ArvadosResource } from "../response";
+import { Collection } from "../../models/collection";
+
+interface CollectionResource extends ArvadosResource {
+    name: string;
+    description: string;
+    properties: any;
+    portable_data_hash: string;
+    manifest_text: string;
+    replication_desired: number;
+    replication_confirmed: number;
+    replication_confirmed_at: string;
+    trash_at: string;
+    delete_at: string;
+    is_trashed: boolean;
+}
+
+interface CollectionsResponse {
+    offset: number;
+    limit: number;
+    items: CollectionResource[];
+}
+
+export default class CollectionService {
+    public getCollectionList = (parentUuid?: string): Promise<Collection[]> => {
+        if (parentUuid) {
+            const fb = new FilterBuilder();
+            fb.addLike(FilterField.OWNER_UUID, parentUuid);
+            return serverApi.get<CollectionsResponse>('/collections', { params: {
+                filters: fb.get()
+            }}).then(resp => {
+                const collections = resp.data.items.map(g => ({
+                    name: g.name,
+                    createdAt: g.created_at,
+                    modifiedAt: g.modified_at,
+                    href: g.href,
+                    uuid: g.uuid,
+                    ownerUuid: g.owner_uuid,
+                    kind: g.kind
+                } as Collection));
+                return collections;
+            });
+        } else {
+            return Promise.resolve([]);
+        }
+    }
+}
index 119cfece4b8fcb130fa69f4ec54304e2f7d270f5..bc34081811fdbbdd6aaa14437087ab82549a08fa 100644 (file)
@@ -4,44 +4,35 @@
 
 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";
+
+interface GroupResource extends ArvadosResource {
+    name: string;
+    group_class: string;
+    description: string;
+    writable_by: string[];
+    delete_at: string;
+    trash_at: string;
+    is_trashed: boolean;
+}
 
 interface GroupsResponse {
     offset: number;
     limit: number;
-    items: Array<{
-        href: string;
-        kind: string;
-        etag: string;
-        uuid: string;
-        owner_uuid: string;
-        created_at: string;
-        modified_by_client_uuid: string;
-        modified_by_user_uuid: string;
-        modified_at: string;
-        name: string;
-        group_class: string;
-        description: string;
-        writable_by: string[];
-        delete_at: string;
-        trash_at: string;
-        is_trashed: boolean;
-    }>;
+    items: GroupResource[];
 }
 
 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);
             return serverApi.get<GroupsResponse>('/groups', { params: {
                 filters: fb.get()
-            }}).then(groups => {
-                const projects = groups.data.items.map(g => ({
+            }}).then(resp => {
+                const projects = resp.data.items.map(g => ({
                     name: g.name,
                     createdAt: g.created_at,
                     modifiedAt: g.modified_at,
@@ -50,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([]);
         }
     }
diff --git a/src/services/response.ts b/src/services/response.ts
new file mode 100644 (file)
index 0000000..a71282b
--- /dev/null
@@ -0,0 +1,11 @@
+export interface ArvadosResource {
+    uuid: string;
+    owner_uuid: string;
+    created_at: string;
+    modified_by_client_uuid: string;
+    modified_by_user_uuid: string;
+    modified_at: string;
+    href: string;
+    kind: string;
+    etag: string;
+}
index ea72001a2849bd983aabbe1c16067c4166b46690..47a24b344aaa7d43c0f257d3c47aabd526995368 100644 (file)
@@ -4,6 +4,8 @@
 
 import AuthService from "./auth-service/auth-service";
 import ProjectService from "./project-service/project-service";
+import CollectionService from "./collection-service/collection-service";
 
 export const authService = new AuthService();
 export const projectService = new ProjectService();
+export const collectionService = new CollectionService();
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};
         },
diff --git a/src/store/collection/collection-action.ts b/src/store/collection/collection-action.ts
new file mode 100644 (file)
index 0000000..f50e645
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+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>(),
+    REMOVE_COLLECTION: ofType<string>(),
+    COLLECTIONS_REQUEST: ofType<any>(),
+    COLLECTIONS_SUCCESS: ofType<{ collections: Collection[] }>(),
+}, {
+    tag: 'type',
+    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;
diff --git a/src/store/collection/collection-reducer.test.ts b/src/store/collection/collection-reducer.test.ts
new file mode 100644 (file)
index 0000000..7b57ba7
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import collectionsReducer from "./collection-reducer";
+import actions from "./collection-action";
+
+describe('collection-reducer', () => {
+    it('should add new collection to the list', () => {
+        const initialState = undefined;
+        const collection = {
+            name: 'test',
+            href: 'href',
+            createdAt: '2018-01-01',
+            modifiedAt: '2018-01-01',
+            ownerUuid: 'owner-test123',
+            uuid: 'test123',
+            kind: ""
+        };
+
+        const state = collectionsReducer(initialState, actions.CREATE_COLLECTION(collection));
+        expect(state).toEqual([collection]);
+    });
+
+    it('should load collections', () => {
+        const initialState = undefined;
+        const collection = {
+            name: 'test',
+            href: 'href',
+            createdAt: '2018-01-01',
+            modifiedAt: '2018-01-01',
+            ownerUuid: 'owner-test123',
+            uuid: 'test123',
+            kind: ""
+        };
+
+        const collections = [collection, collection];
+        const state = collectionsReducer(initialState, actions.COLLECTIONS_SUCCESS({ collections }));
+        expect(state).toEqual([collection, collection]);
+    });
+});
diff --git a/src/store/collection/collection-reducer.ts b/src/store/collection/collection-reducer.ts
new file mode 100644 (file)
index 0000000..939ca62
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import actions, { CollectionAction } from "./collection-action";
+import { Collection } from "../../models/collection";
+
+export type CollectionState = Collection[];
+
+
+const collectionsReducer = (state: CollectionState = [], action: CollectionAction) => {
+    return actions.match(action, {
+        CREATE_COLLECTION: collection => [...state, collection],
+        REMOVE_COLLECTION: () => state,
+        COLLECTIONS_REQUEST: () => {
+            return state;
+        },
+        COLLECTIONS_SUCCESS: ({ collections }) => {
+            return collections;
+        },
+        default: () => state
+    });
+};
+
+export default collectionsReducer;
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 499d89e82fd84dbfd04ef49dbb373d32b4fd1ac2..6b9c31ff4ee3bfca4426d3364b8440625c4d6e19 100644 (file)
@@ -8,6 +8,7 @@ import thunkMiddleware from 'redux-thunk';
 import { History } from "history";
 import projectsReducer, { ProjectState } from "./project/project-reducer";
 import authReducer, { AuthState } from "./auth/auth-reducer";
+import collectionsReducer from "./collection/collection-reducer";
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -23,6 +24,7 @@ export interface RootState {
 const rootReducer = combineReducers({
     auth: authReducer,
     projects: projectsReducer,
+    collections: collectionsReducer,
     router: routerReducer
 });
 
similarity index 77%
rename from src/components/api-token/api-token.tsx
rename to src/views-components/api-token/api-token.tsx
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() {
similarity index 93%
rename from src/components/data-explorer/data-explorer.tsx
rename to src/views-components/data-explorer/data-explorer.tsx
index 874c32595251c7ee169512f5ba93a67212113abe..c4de01a35be7eafc5f6452e6715fbc2cfb47f6b3 100644 (file)
@@ -3,14 +3,17 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { DataTable, DataColumn, ColumnSelector } from "../../components/data-table";
 import { Typography, Grid, Paper, Toolbar } from '@material-ui/core';
 import IconButton from '@material-ui/core/IconButton';
 import MoreVertIcon from "@material-ui/icons/MoreVert";
 import { formatFileSize, formatDate } from '../../common/formatters';
 import { DataItem } from './data-item';
-import { mockAnchorFromMouseEvent } from '../popover/helpers';
-import ContextMenu from '../context-menu/context-menu';
+import { DataColumns } from "../../components/data-table/data-table";
+import ContextMenu from "../../components/context-menu/context-menu";
+import ColumnSelector from "../../components/column-selector/column-selector";
+import DataTable from "../../components/data-table/data-table";
+import { mockAnchorFromMouseEvent } from "../../components/popover/helpers";
+import { DataColumn } from "../../components/data-table/data-column";
 
 export interface DataExplorerContextActions {
     onAddToFavourite: (dataIitem: DataItem) => void;
@@ -28,7 +31,7 @@ interface DataExplorerProps {
 }
 
 interface DataExplorerState {
-    columns: Array<DataColumn<DataItem>>;
+    columns: DataColumns<DataItem>;
     contextMenu: {
         anchorEl?: HTMLElement;
         item?: DataItem;
similarity index 92%
rename from src/components/data-explorer/data-item.ts
rename to src/views-components/data-explorer/data-item.ts
index 3a809924fe1f6e7e6b5e63f32d1b29c192a96087..65e8f632fa22da90c6f00fe3e9af2a0652ff5fe6 100644 (file)
@@ -3,10 +3,11 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 export interface DataItem {
+    uuid: string;
     name: string;
     type: string;
     owner: string;
     lastModified: string;
     fileSize?: number;
     status?: string;
-}
\ No newline at end of file
+}
similarity index 95%
rename from src/components/main-app-bar/main-app-bar.test.tsx
rename to src/views-components/main-app-bar/main-app-bar.test.tsx
index f08c9392857e8ae37e4edb3a675f7d5e83cedfc2..25494b65a07c2e9c76757f84a25a26400a196ead 100644 (file)
@@ -6,9 +6,9 @@ import * as React from "react";
 import { mount, configure, ReactWrapper } from "enzyme";
 import * as Adapter from "enzyme-adapter-react-16";
 import MainAppBar from "./main-app-bar";
-import SearchBar from "./search-bar/search-bar";
-import Breadcrumbs from "../breadcrumbs/breadcrumbs";
-import DropdownMenu from "./dropdown-menu/dropdown-menu";
+import SearchBar from "../../components/search-bar/search-bar";
+import Breadcrumbs from "../../components/breadcrumbs/breadcrumbs";
+import DropdownMenu from "../../components/dropdown-menu/dropdown-menu";
 import { Button, MenuItem, IconButton } from "@material-ui/core";
 import { User } from "../../models/user";
 
@@ -98,4 +98,4 @@ describe("<MainAppBar />", () => {
         mainAppBar.find(DropdownMenu).at(0).find(MenuItem).at(1).simulate("click");
         expect(onMenuItemClick).toBeCalledWith(menuItems.accountMenu[0]);
     });
-});
\ No newline at end of file
+});
similarity index 94%
rename from src/components/main-app-bar/main-app-bar.tsx
rename to src/views-components/main-app-bar/main-app-bar.tsx
index 27cd8bd4d3df0b1196dd2fcb78c1ebee730a4689..c0525a566843e9a10f75b0a38fb84babef482c38 100644 (file)
@@ -7,9 +7,9 @@ import { AppBar, Toolbar, Typography, Grid, IconButton, Badge, StyleRulesCallbac
 import NotificationsIcon from "@material-ui/icons/Notifications";
 import PersonIcon from "@material-ui/icons/Person";
 import HelpIcon from "@material-ui/icons/Help";
-import SearchBar from "./search-bar/search-bar";
-import Breadcrumbs, { Breadcrumb } from "../breadcrumbs/breadcrumbs";
-import DropdownMenu from "./dropdown-menu/dropdown-menu";
+import SearchBar from "../../components/search-bar/search-bar";
+import Breadcrumbs, { Breadcrumb } from "../../components/breadcrumbs/breadcrumbs";
+import DropdownMenu from "../../components/dropdown-menu/dropdown-menu";
 import { User, getUserFullname } from "../../models/user";
 
 export interface MainAppBarMenuItem {
@@ -126,4 +126,4 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     }
 });
 
-export default withStyles(styles)(MainAppBar);
\ No newline at end of file
+export default withStyles(styles)(MainAppBar);
similarity index 98%
rename from src/components/project-tree/project-tree.test.tsx
rename to src/views-components/project-tree/project-tree.test.tsx
index 932a29cc16793aede03e3dd035031cfa0a542d6b..d53121304c817a83b113406b5451f8c433ca37b7 100644 (file)
@@ -11,7 +11,7 @@ import { Collapse } from '@material-ui/core';
 import CircularProgress from '@material-ui/core/CircularProgress';
 
 import ProjectTree from './project-tree';
-import { TreeItem } from '../tree/tree';
+import { TreeItem } from '../../components/tree/tree';
 import { Project } from '../../models/project';
 Enzyme.configure({ adapter: new Adapter() });
 
similarity index 96%
rename from src/components/project-tree/project-tree.tsx
rename to src/views-components/project-tree/project-tree.tsx
index 275805ffa9555f2d8f965311690c0bb76cdcf043..fd32ff040d82dc5dce7f8bbc94583c88357695dd 100644 (file)
@@ -9,7 +9,7 @@ import ListItemText from "@material-ui/core/ListItemText/ListItemText";
 import ListItemIcon from '@material-ui/core/ListItemIcon';
 import Typography from '@material-ui/core/Typography';
 
-import Tree, { TreeItem, TreeItemStatus } from '../tree/tree';
+import Tree, { TreeItem, TreeItemStatus } from '../../components/tree/tree';
 import { Project } from '../../models/project';
 
 type CssRules = 'active' | 'listItemText' | 'row' | 'treeContainer';
diff --git a/src/views/data-explorer/data-explorer-selectors.ts b/src/views/data-explorer/data-explorer-selectors.ts
new file mode 100644 (file)
index 0000000..5f17037
--- /dev/null
@@ -0,0 +1,11 @@
+import { TreeItem } from "../../components/tree/tree";
+import { Project } from "../../models/project";
+import { DataItem } from "../../views-components/data-explorer/data-item";
+
+export const mapProjectTreeItem = (item: TreeItem<Project>): DataItem => ({
+    name: item.data.name,
+    type: item.data.kind,
+    owner: item.data.ownerUuid,
+    lastModified: item.data.modifiedAt,
+    uuid: item.data.uuid
+});
index da09b695dbb047f2bf564a5edb7c94ec264feaa2..f4ee36f3b4af2542add0564b6405a075d1e38eaf 100644 (file)
@@ -3,7 +3,6 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { DataTableProps } from "../../components/data-table";
 import { RouteComponentProps } from 'react-router';
 import { Project } from '../../models/project';
 import { ProjectState, findTreeItem } from '../../store/project/project-reducer';
@@ -11,21 +10,17 @@ import { RootState } from '../../store/store';
 import { connect, DispatchProp } from 'react-redux';
 import { push } from 'react-router-redux';
 import projectActions from "../../store/project/project-action";
-import { DataExplorer, DataItem } from '../../components/data-explorer';
-import { TreeItem } from '../../components/tree/tree';
-import { DataExplorerContextActions } from '../../components/data-explorer/data-explorer';
+import { DataColumns } from "../../components/data-table/data-table";
+import DataExplorer, { DataExplorerContextActions } from "../../views-components/data-explorer/data-explorer";
+import { mapProjectTreeItem } from "./data-explorer-selectors";
+import { DataItem } from "../../views-components/data-explorer/data-item";
 
 interface DataExplorerViewDataProps {
     projects: ProjectState;
 }
 
 type DataExplorerViewProps = DataExplorerViewDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
-
-type DataExplorerViewState = Pick<DataTableProps<Project>, "columns">;
-
-interface MappedProjectItem extends DataItem {
-    uuid: string;
-}
+type DataExplorerViewState = DataColumns<Project>;
 
 class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplorerViewState> {
 
@@ -34,7 +29,7 @@ class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplor
         const projectItems = project && project.items || [];
         return (
             <DataExplorer
-                items={projectItems.map(mapTreeItem)}
+                items={projectItems.map(mapProjectTreeItem)}
                 onItemClick={this.goToProject}
                 contextActions={this.contextActions}
             />
@@ -51,22 +46,12 @@ class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplor
         onShare: console.log
     };
 
-    goToProject = (project: MappedProjectItem) => {
-        this.props.dispatch(push(`/project/${project.uuid}`));
-        this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(project.uuid));
+    goToProject = (item: DataItem) => {
+        this.props.dispatch(push(`/project/${item}`));
+        this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(item.uuid));
     }
-
 }
 
-const mapTreeItem = (item: TreeItem<Project>): MappedProjectItem => ({
-    name: item.data.name,
-    type: item.data.kind,
-    owner: item.data.ownerUuid,
-    lastModified: item.data.modifiedAt,
-    uuid: item.data.uuid
-});
-
-
 export default connect(
     (state: RootState) => ({
         projects: state.projects
index f1f00885ec8015dab092f0842e4818e059d3eab4..41725b53ef427aa613e657d37d5f6d88cb024738 100644 (file)
@@ -11,14 +11,13 @@ import { Route, Switch } from "react-router";
 import authActions from "../../store/auth/auth-action";
 import { User } from "../../models/user";
 import { RootState } from "../../store/store";
-import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar';
+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 ProjectTree from '../../components/project-tree/project-tree';
+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';
 
@@ -139,7 +138,8 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
         if (status === TreeItemStatus.Loaded) {
             this.openProjectItem(itemId);
         } else {
-            this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => this.openProjectItem(itemId));
+            this.props.dispatch<any>(getProjectList(itemId))
+                .then(() => this.openProjectItem(itemId));
         }
     }