Unify use of Collection and Project resource types
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 12 Jul 2018 12:34:21 +0000 (14:34 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 12 Jul 2018 12:34:21 +0000 (14:34 +0200)
Feature #13798

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

15 files changed:
src/models/collection.ts
src/models/project.ts
src/models/test-utils.ts [new file with mode: 0644]
src/services/collection-service/collection-service.ts [deleted file]
src/services/services.ts
src/store/collection/collection-action.ts [deleted file]
src/store/collection/collection-reducer.test.ts [deleted file]
src/store/collection/collection-reducer.ts [deleted file]
src/store/navigation/navigation-action.ts
src/store/project/project-action.ts
src/store/project/project-reducer.test.ts
src/store/project/project-reducer.ts
src/store/store.ts
src/views-components/project-tree/project-tree.tsx
src/views/workbench/workbench.tsx

index cf5b4e68ce0d2d36331715461a4879b593cd5dcb..8effd8ee6448b1dfd2e7dfc60acb574bd2a91f3c 100644 (file)
@@ -2,13 +2,9 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Resource as R } from "./resource";
 import { Resource } from "../common/api/common-resource-service";
 import { ResourceKind } from "./kinds";
 
-export interface Collection extends R {
-}
-
 export interface CollectionResource extends Resource {
     kind: ResourceKind.Collection;
     name: string;
index c44c8cc7974a9162583ef6b01cfbb3e81281734d..1373ca7d509976d82962efd9cc48f17e07f90aa3 100644 (file)
@@ -2,12 +2,8 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Resource as R } from "./resource";
 import { GroupResource, GroupClass } from "./group";
 
-export interface Project extends R {
-}
-
 export interface ProjectResource extends GroupResource {
     groupClass: GroupClass.Project;
 }
diff --git a/src/models/test-utils.ts b/src/models/test-utils.ts
new file mode 100644 (file)
index 0000000..aa8d96b
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { GroupClass, GroupResource } from "./group";
+import { ResourceKind } from "./kinds";
+import { Resource } from "../common/api/common-resource-service";
+
+type ResourceUnion = GroupResource;
+
+export const mockResource = (kind: ResourceKind, data: Partial<Exclude<ResourceUnion, "kind">>) => {
+    switch (kind) {
+        case ResourceKind.Group:
+            return mockGroupResource({ ...data, kind });
+        default:
+            return mockCommonResource({ ...data, kind });
+    }
+};
+
+export const mockGroupResource = (data: Partial<Exclude<GroupResource, "kind">>): GroupResource => ({
+    createdAt: "",
+    deleteAt: "",
+    description: "",
+    etag: "",
+    groupClass: null,
+    href: "",
+    isTrashed: false,
+    kind: ResourceKind.Group,
+    modifiedAt: "",
+    modifiedByClientUuid: "",
+    modifiedByUserUuid: "",
+    name: "",
+    ownerUuid: "",
+    properties: "",
+    trashAt: "",
+    uuid: "",
+    writeableBy: []
+});
+
+const mockCommonResource = <T extends Resource>(data: Partial<T> & { kind: ResourceKind }): Resource => ({
+    createdAt: "",
+    etag: "",
+    href: "",
+    kind: data.kind,
+    modifiedAt: "",
+    modifiedByClientUuid: "",
+    modifiedByUserUuid: "",
+    ownerUuid: "",
+    uuid: ""
+});
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
deleted file mode 100644 (file)
index 8412dea..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { serverApi } from "../../common/api/server-api";
-import FilterBuilder from "../../common/api/filter-builder";
-import { ArvadosResource } from "../response";
-import { Collection } from "../../models/collection";
-import { getResourceKind } from "../../models/resource";
-
-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("ownerUuid", parentUuid);
-            return serverApi.get<CollectionsResponse>('/collections', { params: {
-                filters: fb.serialize()
-            }}).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: getResourceKind(g.kind)
-                } as Collection));
-                return collections;
-            });
-        } else {
-            return Promise.resolve([]);
-        }
-    }
-}
index 1e9a74dd3fbf3b22d50c4ea8faec96f5a8452cff..143e97bdaf2dbbeff82b3e2cb5b23a691a0505e0 100644 (file)
@@ -3,12 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import AuthService from "./auth-service/auth-service";
-import CollectionService from "./collection-service/collection-service";
 import GroupsService from "./groups-service/groups-service";
 import { serverApi } from "../common/api/server-api";
 import ProjectService from "./project-service/project-service";
 
 export const authService = new AuthService();
-export const collectionService = new CollectionService();
 export const groupsService = new GroupsService(serverApi);
 export const projectService = new ProjectService(serverApi);
diff --git a/src/store/collection/collection-action.ts b/src/store/collection/collection-action.ts
deleted file mode 100644 (file)
index f50e645..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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
deleted file mode 100644 (file)
index 7bc1aec..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import collectionsReducer from "./collection-reducer";
-import actions from "./collection-action";
-import { ResourceKind } from "../../models/resource";
-
-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: ResourceKind.COLLECTION
-        };
-
-        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: ResourceKind.COLLECTION
-        };
-
-        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
deleted file mode 100644 (file)
index 5c257ea..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 [];
-        },
-        COLLECTIONS_SUCCESS: ({ collections }) => {
-            return collections;
-        },
-        default: () => state
-    });
-};
-
-export default collectionsReducer;
index 7ba245a1c7726900b68593617dee60bae5aa535b..25cc0034deff7732ba2e190777db993bed2fb258 100644 (file)
@@ -7,20 +7,17 @@ import projectActions, { getProjectList } from "../project/project-action";
 import { push } from "react-router-redux";
 import { TreeItemStatus } from "../../components/tree/tree";
 import { findTreeItem } from "../project/project-reducer";
-import { Resource, ResourceKind as R } from "../../models/resource";
-import sidePanelActions from "../side-panel/side-panel-action";
 import dataExplorerActions from "../data-explorer/data-explorer-action";
 import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
 import { RootState } from "../store";
-import { sidePanelData } from "../side-panel/side-panel-reducer";
-import { loadDetails } from "../details-panel/details-panel-action";
+import { Resource } from "../../common/api/common-resource-service";
 import { ResourceKind } from "../../models/kinds";
 
-export const getResourceUrl = (resource: Resource): string => {
+export const getResourceUrl = <T extends Resource>(resource: T): string => {
     switch (resource.kind) {
-        case R.PROJECT: return `/projects/${resource.uuid}`;
-        case R.COLLECTION: return `/collections/${resource.uuid}`;
-        default: return "";
+        case ResourceKind.Project: return `/projects/${resource.uuid}`;
+        case ResourceKind.Collection: return `/collections/${resource.uuid}`;
+        default: return resource.href;
     }
 };
 
@@ -32,7 +29,7 @@ export enum ItemMode {
 
 export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
     (dispatch: Dispatch, getState: () => RootState) => {
-        const { projects, router, sidePanel } = getState();
+        const { projects, router } = getState();
         const treeItem = findTreeItem(projects.items, itemId);
 
         if (treeItem) {
@@ -41,7 +38,7 @@ export const setProjectItem = (itemId: string, itemMode: ItemMode) =>
                 dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(treeItem.data.uuid));
             }
 
-            const resourceUrl = getResourceUrl({ ...treeItem.data });
+            const resourceUrl = getResourceUrl(treeItem.data);
 
             if (itemMode === ItemMode.ACTIVE || itemMode === ItemMode.BOTH) {
                 if (router.location && !router.location.pathname.includes(resourceUrl)) {
index c1a002f98a760d109c03bdbd022eeb36b7a94a10..3acf091b3fd8e8f88d1b57e5bdc65f9648fc9b71 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 import { default as unionize, ofType, UnionOf } from "unionize";
 
-import { Project, ProjectResource } from "../../models/project";
+import { ProjectResource } from "../../models/project";
 import { projectService } from "../../services/services";
 import { Dispatch } from "redux";
 import { getResourceKind } from "../../models/resource";
@@ -19,7 +19,7 @@ const actions = unionize({
     CREATE_PROJECT_ERROR: ofType<string>(),
     REMOVE_PROJECT: ofType<string>(),
     PROJECTS_REQUEST: ofType<string>(),
-    PROJECTS_SUCCESS: ofType<{ projects: Project[], parentItemId?: string }>(),
+    PROJECTS_SUCCESS: ofType<{ projects: ProjectResource[], parentItemId?: string }>(),
     TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType<string>(),
     TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType<string>(),
     RESET_PROJECT_TREE_ACTIVITY: ofType<string>()
@@ -34,11 +34,7 @@ export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch)
         filters: FilterBuilder
             .create<ProjectResource>()
             .addEqual("ownerUuid", parentUuid)
-    }).then(listResults => {
-        const projects = listResults.items.map(item => ({
-            ...item,
-            kind: getResourceKind(item.kind)
-        }));
+    }).then(({ items: projects }) => {
         dispatch(actions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid }));
         return projects;
     });
index f14ee9a8ef055665033286ef4593469754151198..b2def8197c784458e8282e3728b88879475ad3bb 100644 (file)
@@ -5,21 +5,16 @@
 import projectsReducer, { getTreePath } from "./project-reducer";
 import actions from "./project-action";
 import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
-import { ResourceKind } from "../../models/resource";
+import { ResourceKind } from "../../models/kinds";
+import { mockResource } from "../../models/test-utils";
+import { ProjectResource } from "../../models/project";
+import { GroupClass } from "../../models/group";
 
 describe('project-reducer', () => {
 
     it('should load projects', () => {
         const initialState = undefined;
-        const project = {
-            name: 'test',
-            href: 'href',
-            createdAt: '2018-01-01',
-            modifiedAt: '2018-01-01',
-            ownerUuid: 'owner-test123',
-            uuid: 'test123',
-            kind: ResourceKind.PROJECT
-        };
+        const project = mockProject({ ownerUuid: "test123" });
 
         const projects = [project, project];
         const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({ projects, parentItemId: undefined }));
@@ -52,15 +47,7 @@ describe('project-reducer', () => {
     it('should remove activity on projects list', () => {
         const initialState = {
             items: [{
-                data: {
-                    name: 'test',
-                    href: 'href',
-                    createdAt: '2018-01-01',
-                    modifiedAt: '2018-01-01',
-                    ownerUuid: 'owner-test123',
-                    uuid: 'test123',
-                    kind: ResourceKind.PROJECT
-                },
+                data: mockProject(),
                 id: "1",
                 open: true,
                 active: true,
@@ -71,15 +58,7 @@ describe('project-reducer', () => {
         };
         const project = {
             items: [{
-                data: {
-                    name: 'test',
-                    href: 'href',
-                    createdAt: '2018-01-01',
-                    modifiedAt: '2018-01-01',
-                    ownerUuid: 'owner-test123',
-                    uuid: 'test123',
-                    kind: ResourceKind.PROJECT
-                },
+                data: { ...initialState.items[0] },
                 id: "1",
                 open: true,
                 active: false,
@@ -96,15 +75,7 @@ describe('project-reducer', () => {
     it('should toggle project tree item activity', () => {
         const initialState = {
             items: [{
-                data: {
-                    name: 'test',
-                    href: 'href',
-                    createdAt: '2018-01-01',
-                    modifiedAt: '2018-01-01',
-                    ownerUuid: 'owner-test123',
-                    uuid: 'test123',
-                    kind: ResourceKind.PROJECT
-                },
+                data: mockProject(),
                 id: "1",
                 open: true,
                 active: false,
@@ -115,15 +86,7 @@ describe('project-reducer', () => {
         };
         const project = {
             items: [{
-                data: {
-                    name: 'test',
-                    href: 'href',
-                    createdAt: '2018-01-01',
-                    modifiedAt: '2018-01-01',
-                    ownerUuid: 'owner-test123',
-                    uuid: 'test123',
-                    kind: ResourceKind.PROJECT,
-                },
+                data: { ...initialState.items[0] },
                 id: "1",
                 open: true,
                 active: true,
@@ -142,15 +105,7 @@ describe('project-reducer', () => {
     it('should close project tree item ', () => {
         const initialState = {
             items: [{
-                data: {
-                    name: 'test',
-                    href: 'href',
-                    createdAt: '2018-01-01',
-                    modifiedAt: '2018-01-01',
-                    ownerUuid: 'owner-test123',
-                    uuid: 'test123',
-                    kind: ResourceKind.PROJECT
-                },
+                data: mockProject(),
                 id: "1",
                 open: true,
                 active: false,
@@ -162,15 +117,7 @@ describe('project-reducer', () => {
         };
         const project = {
             items: [{
-                data: {
-                    name: 'test',
-                    href: 'href',
-                    createdAt: '2018-01-01',
-                    modifiedAt: '2018-01-01',
-                    ownerUuid: 'owner-test123',
-                    uuid: 'test123',
-                    kind: ResourceKind.PROJECT
-                },
+                data: { ...initialState.items[0] },
                 id: "1",
                 open: false,
                 active: false,
@@ -234,3 +181,5 @@ describe("findTreeBranch", () => {
     });
 
 });
+
+const mockProject = (data: Partial<ProjectResource> = {}) => mockResource(ResourceKind.Group, { ...data, groupClass: GroupClass.Project }) as ProjectResource;
index 5693fb64262326cbecf61f48d1b57f4ab4acd6dd..c008370d9a28e91b8a470b8b3c0fa9bf911d6b11 100644 (file)
@@ -4,12 +4,12 @@
 
 import * as _ from "lodash";
 
-import { Project } from "../../models/project";
 import actions, { ProjectAction } from "./project-action";
 import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
+import { ProjectResource } from "../../models/project";
 
 export type ProjectState = {
-    items: Array<TreeItem<Project>>,
+    items: Array<TreeItem<ProjectResource>>,
     currentItemId: string,
     creator: ProjectCreator
 };
@@ -67,7 +67,7 @@ function resetTreeActivity<T>(tree: Array<TreeItem<T>>) {
     }
 }
 
-function updateProjectTree(tree: Array<TreeItem<Project>>, projects: Project[], parentItemId?: string): Array<TreeItem<Project>> {
+function updateProjectTree(tree: Array<TreeItem<ProjectResource>>, projects: ProjectResource[], parentItemId?: string): Array<TreeItem<ProjectResource>> {
     let treeItem;
     if (parentItemId) {
         treeItem = findTreeItem(tree, parentItemId);
@@ -82,7 +82,7 @@ function updateProjectTree(tree: Array<TreeItem<Project>>, projects: Project[],
         status: TreeItemStatus.Initial,
         data: p,
         items: []
-    } as TreeItem<Project>));
+    } as TreeItem<ProjectResource>));
 
     if (treeItem) {
         treeItem.items = items;
index f74b87737ed14c6b94b751145f2e258006c81f45..36f92034ac07ab48aed7809a3247aeb28920bbb3 100644 (file)
@@ -11,7 +11,6 @@ import projectsReducer, { ProjectState } from "./project/project-reducer";
 import sidePanelReducer, { SidePanelState } from './side-panel/side-panel-reducer';
 import authReducer, { AuthState } from "./auth/auth-reducer";
 import dataExplorerReducer, { DataExplorerState } from './data-explorer/data-explorer-reducer';
-import collectionsReducer, { CollectionState } from "./collection/collection-reducer";
 import { projectPanelMiddleware } from '../store/project-panel/project-panel-middleware';
 import detailsPanelReducer, { DetailsPanelState } from './details-panel/details-panel-reducer';
 
@@ -23,7 +22,6 @@ const composeEnhancers =
 export interface RootState {
     auth: AuthState;
     projects: ProjectState;
-    collections: CollectionState;
     router: RouterState;
     dataExplorer: DataExplorerState;
     sidePanel: SidePanelState;
@@ -33,7 +31,6 @@ export interface RootState {
 const rootReducer = combineReducers({
     auth: authReducer,
     projects: projectsReducer,
-    collections: collectionsReducer,
     router: routerReducer,
     dataExplorer: dataExplorerReducer,
     sidePanel: sidePanelReducer,
index 511cbbbc7380198e12eec9f6f7f48bcffd99efae..17592a7f36d9374e8c6bef603e132d9690567944 100644 (file)
@@ -10,13 +10,13 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
 import Typography from '@material-ui/core/Typography';
 
 import Tree, { TreeItem, TreeItemStatus } from '../../components/tree/tree';
-import { Project } from '../../models/project';
+import { ProjectResource } from '../../models/project';
 
 export interface ProjectTreeProps {
-    projects: Array<TreeItem<Project>>;
+    projects: Array<TreeItem<ProjectResource>>;
     toggleOpen: (id: string, status: TreeItemStatus) => void;
     toggleActive: (id: string, status: TreeItemStatus) => void;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<Project>) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<ProjectResource>) => void;
 }
 
 class ProjectTree<T> extends React.Component<ProjectTreeProps & WithStyles<CssRules>> {
@@ -29,7 +29,7 @@ class ProjectTree<T> extends React.Component<ProjectTreeProps & WithStyles<CssRu
                     onContextMenu={onContextMenu}
                     toggleItemOpen={toggleOpen}
                     toggleItemActive={toggleActive}
-                    render={(project: TreeItem<Project>) =>
+                    render={(project: TreeItem<ProjectResource>) =>
                         <span className={row}>
                             <ListItemIcon className={project.active ? active : ''}>
                                 <i className="fas fa-folder" />
index e7a8ae3088d44192d67fc7a0d401b27f92ecac2a..75cc3363c207f9295bb359d9c3119ebf9cf886e9 100644 (file)
@@ -18,7 +18,6 @@ import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
 import { push } from 'react-router-redux';
 import ProjectTree from '../../views-components/project-tree/project-tree';
 import { TreeItem } from "../../components/tree/tree";
-import { Project } from "../../models/project";
 import { getTreePath } from '../../store/project/project-reducer';
 import sidePanelActions from '../../store/side-panel/side-panel-action';
 import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel';
@@ -35,6 +34,7 @@ import { authService } from '../../services/services';
 import detailsPanelActions, { loadDetails } from "../../store/details-panel/details-panel-action";
 import { ResourceKind } from '../../models/kinds';
 import { SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer';
+import { ProjectResource } from '../../models/project';
 
 const drawerWidth = 240;
 const appBarHeight = 100;
@@ -78,7 +78,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 });
 
 interface WorkbenchDataProps {
-    projects: Array<TreeItem<Project>>;
+    projects: Array<TreeItem<ProjectResource>>;
     currentProjectId: string;
     user?: User;
     sidePanelItems: SidePanelItem[];