[ARVADOS-WORKBENCH2] created: 1.1.4-437-g46460de

Git user git at public.curoverse.com
Fri Jul 27 10:38:02 EDT 2018


        at  46460de37ddbc61a296d3a393bce332d856664a6 (commit)


commit 46460de37ddbc61a296d3a393bce332d856664a6
Author: Pawel Kowalczyk <pawel.kowalczyk at contractors.roche.com>
Date:   Fri Jul 27 16:37:40 2018 +0200

    collection-creation-without-tests+fixed-disabling-button-on-validation
    
    Feature #13893
    
    Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk at contractors.roche.com>

diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
new file mode 100644
index 0000000..de52e64
--- /dev/null
+++ b/src/services/collection-service/collection-service.ts
@@ -0,0 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { CommonResourceService } from "../../common/api/common-resource-service";
+import { CollectionResource } from "../../models/collection";
+import { AxiosInstance } from "axios";
+
+export class CollectionCreationService extends CommonResourceService<CollectionResource> {
+    constructor(serverApi: AxiosInstance) {
+        super(serverApi, "collections");
+    }
+}
\ No newline at end of file
diff --git a/src/services/services.ts b/src/services/services.ts
index a08ed3c..4f191bb 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -8,9 +8,11 @@ import { authClient, apiClient } from "../common/api/server-api";
 import { ProjectService } from "./project-service/project-service";
 import { LinkService } from "./link-service/link-service";
 import { FavoriteService } from "./favorite-service/favorite-service";
+import { CollectionCreationService } from "./collection-service/collection-service";
 
 export const authService = new AuthService(authClient, apiClient);
 export const groupsService = new GroupsService(apiClient);
 export const projectService = new ProjectService(apiClient);
+export const collectionCreationService = new CollectionCreationService(apiClient);
 export const linkService = new LinkService(apiClient);
 export const favoriteService = new FavoriteService(linkService, groupsService);
diff --git a/src/store/collections/creator/collection-creator-action.ts b/src/store/collections/creator/collection-creator-action.ts
new file mode 100644
index 0000000..d21d622
--- /dev/null
+++ b/src/store/collections/creator/collection-creator-action.ts
@@ -0,0 +1,32 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { default as unionize, ofType, UnionOf } from "unionize";
+import { Dispatch } from "redux";
+
+import { RootState } from "../../store";
+import { collectionCreationService } from '../../../services/services';
+import { CollectionResource } from '../../../models/collection';
+
+export const collectionCreateActions = unionize({
+    OPEN_COLLECTION_CREATOR: ofType<{ ownerUuid: string }>(),
+    CLOSE_COLLECTION_CREATOR: ofType<{}>(),
+    CREATE_COLLECTION: ofType<{}>(),
+    CREATE_COLLECTION_SUCCESS: ofType<{}>(),
+}, {
+        tag: 'type',
+        value: 'payload'
+    });
+
+export const createCollection = (collection: Partial<CollectionResource>) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const { ownerUuid } = getState().collectionCreation.creator;
+        const collectiontData = { ownerUuid, ...collection };
+        dispatch(collectionCreateActions.CREATE_COLLECTION(collectiontData));
+        return collectionCreationService
+            .create(collectiontData)
+            .then(collection => dispatch(collectionCreateActions.CREATE_COLLECTION_SUCCESS(collection)));
+    };
+
+export type CollectionCreateAction = UnionOf<typeof collectionCreateActions>;
\ No newline at end of file
diff --git a/src/store/collections/creator/collection-creator-reducer.test.ts b/src/store/collections/creator/collection-creator-reducer.test.ts
new file mode 100644
index 0000000..df2c362
--- /dev/null
+++ b/src/store/collections/creator/collection-creator-reducer.test.ts
@@ -0,0 +1,3 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
\ No newline at end of file
diff --git a/src/store/collections/creator/collection-creator-reducer.ts b/src/store/collections/creator/collection-creator-reducer.ts
new file mode 100644
index 0000000..c785480
--- /dev/null
+++ b/src/store/collections/creator/collection-creator-reducer.ts
@@ -0,0 +1,41 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { collectionCreateActions, CollectionCreateAction } from './collection-creator-action';
+
+export type CollectionCreatorState = {
+    creator: CollectionCreator
+};
+
+interface CollectionCreator {
+    opened: boolean;
+    pending: boolean;
+    ownerUuid: string;
+}
+
+const updateCreator = (state: CollectionCreatorState, creator: Partial<CollectionCreator>) => ({
+    ...state,
+    creator: {
+        ...state.creator,
+        ...creator
+    }
+});
+
+const initialState: CollectionCreatorState = {
+    creator: {
+        opened: false,
+        pending: false,
+        ownerUuid: ""
+    }
+};
+
+export const collectionCreationReducer = (state: CollectionCreatorState = initialState, action: CollectionCreateAction) => {
+    return collectionCreateActions.match(action, {
+        OPEN_COLLECTION_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true, pending: false }),
+        CLOSE_COLLECTION_CREATOR: () => updateCreator(state, { opened: false }),
+        CREATE_COLLECTION: () => updateCreator(state, { opened: true }),
+        CREATE_COLLECTION_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }),
+        default: () => state
+    });
+};
diff --git a/src/store/store.ts b/src/store/store.ts
index ae07744..1a2dd12 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 { CollectionCreatorState, collectionCreationReducer } from './collections/creator/collection-creator-reducer';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -27,6 +28,7 @@ const composeEnhancers =
 export interface RootState {
     auth: AuthState;
     projects: ProjectState;
+    collectionCreation: CollectionCreatorState;
     router: RouterState;
     dataExplorer: DataExplorerState;
     sidePanel: SidePanelState;
@@ -39,6 +41,7 @@ export interface RootState {
 const rootReducer = combineReducers({
     auth: authReducer,
     projects: projectsReducer,
+    collectionCreation: collectionCreationReducer,
     router: routerReducer,
     dataExplorer: dataExplorerReducer,
     sidePanel: sidePanelReducer,
diff --git a/src/validators/create-project/create-project-validator.tsx b/src/validators/create-project/create-project-validator.tsx
index 928efdd..527043d 100644
--- a/src/validators/create-project/create-project-validator.tsx
+++ b/src/validators/create-project/create-project-validator.tsx
@@ -7,3 +7,5 @@ import { maxLength } from '../max-length';
 
 export const PROJECT_NAME_VALIDATION = [require, maxLength(255)];
 export const PROJECT_DESCRIPTION_VALIDATION = [maxLength(255)];
+export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
+export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
diff --git a/src/views-components/create-collection-dialog/create-collection-dialog.tsx b/src/views-components/create-collection-dialog/create-collection-dialog.tsx
new file mode 100644
index 0000000..98b57c5
--- /dev/null
+++ b/src/views-components/create-collection-dialog/create-collection-dialog.tsx
@@ -0,0 +1,39 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from "react-redux";
+import { Dispatch } from "redux";
+import { SubmissionError } from "redux-form";
+
+import { RootState } from "../../store/store";
+import { DialogCollectionCreate } from "../dialog-create/dialog-collection-create";
+import { collectionCreateActions, createCollection } from "../../store/collections/creator/collection-creator-action";
+import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action";
+import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
+
+const mapStateToProps = (state: RootState) => ({
+    open: state.collectionCreation.creator.opened
+});
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+    handleClose: () => {
+        dispatch(collectionCreateActions.CLOSE_COLLECTION_CREATOR());
+    },
+    onSubmit: (data: { name: string, description: string }) => {
+        return dispatch<any>(addCollection(data))
+            .catch((e: any) => {
+                throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Collection with this name already exists." : "" });
+            });
+    }
+});
+
+const addCollection = (data: { name: string, description: string }) =>
+    (dispatch: Dispatch) => {
+        return dispatch<any>(createCollection(data)).then(() => {
+            dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+        });
+    };
+
+export const CreateCollectionDialog = connect(mapStateToProps, mapDispatchToProps)(DialogCollectionCreate);
+
diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-collection-create.tsx
similarity index 80%
copy from src/views-components/dialog-create/dialog-project-create.tsx
copy to src/views-components/dialog-create/dialog-collection-create.tsx
index 592efc1..8ce0c5d 100644
--- a/src/views-components/dialog-create/dialog-project-create.tsx
+++ b/src/views-components/dialog-create/dialog-collection-create.tsx
@@ -12,7 +12,7 @@ import DialogContent from '@material-ui/core/DialogContent';
 import DialogTitle from '@material-ui/core/DialogTitle';
 import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
 
-import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '../../validators/create-project/create-project-validator';
+import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '../../validators/create-project/create-project-validator';
 
 type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
 
@@ -48,12 +48,14 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
         marginBottom: "24px"
     }
 });
-interface DialogProjectProps {
+interface DialogCollectionCreateProps {
     open: boolean;
     handleClose: () => void;
     onSubmit: (data: { name: string, description: string }) => void;
     handleSubmit: any;
     submitting: boolean;
+    invalid: boolean;
+    pristine: boolean;
 }
 
 interface TextFieldProps {
@@ -64,12 +66,12 @@ interface TextFieldProps {
     meta?: any;
 }
 
-export const DialogProjectCreate = compose(
-    reduxForm({ form: 'projectCreateDialog' }),
+export const DialogCollectionCreate = compose(
+    reduxForm({ form: 'collectionCreateDialog' }),
     withStyles(styles))(
-    class DialogProjectCreate extends React.Component<DialogProjectProps & WithStyles<CssRules>> {
+    class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & WithStyles<CssRules>> {
         render() {
-            const { classes, open, handleClose, handleSubmit, onSubmit, submitting } = this.props;
+            const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props;
 
             return (
                 <Dialog
@@ -79,19 +81,18 @@ export const DialogProjectCreate = compose(
                     disableEscapeKeyDown={true}>
                     <div className={classes.dialog}>
                         <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
-                            <DialogTitle id="form-dialog-title" className={classes.dialogTitle}>Create a
-                                project</DialogTitle>
+                            <DialogTitle id="form-dialog-title" className={classes.dialogTitle}>Create a collection</DialogTitle>
                             <DialogContent className={classes.formContainer}>
                                 <Field name="name"
                                        component={this.renderTextField}
-                                       floatinglabeltext="Project Name"
-                                       validate={PROJECT_NAME_VALIDATION}
+                                       floatinglabeltext="Collection Name"
+                                       validate={COLLECTION_NAME_VALIDATION}
                                        className={classes.textField}
-                                       label="Project Name"/>
+                                       label="Collection Name"/>
                                 <Field name="description"
                                        component={this.renderTextField}
                                        floatinglabeltext="Description - optional"
-                                       validate={PROJECT_DESCRIPTION_VALIDATION}
+                                       validate={COLLECTION_DESCRIPTION_VALIDATION}
                                        className={classes.textField}
                                        label="Description - optional"/>
                             </DialogContent>
@@ -101,9 +102,9 @@ export const DialogProjectCreate = compose(
                                 <Button type="submit"
                                         className={classes.lastButton}
                                         color="primary"
-                                        disabled={submitting}
+                                        disabled={invalid|| submitting || pristine}
                                         variant="contained">
-                                    CREATE A PROJECT
+                                    CREATE A COLLECTION
                                 </Button>
                                 {submitting && <CircularProgress size={20} className={classes.createProgress}/>}
                             </DialogActions>
diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx
index 592efc1..e05a3bd 100644
--- a/src/views-components/dialog-create/dialog-project-create.tsx
+++ b/src/views-components/dialog-create/dialog-project-create.tsx
@@ -54,6 +54,8 @@ interface DialogProjectProps {
     onSubmit: (data: { name: string, description: string }) => void;
     handleSubmit: any;
     submitting: boolean;
+    invalid: boolean;
+    pristine: boolean;
 }
 
 interface TextFieldProps {
@@ -69,7 +71,7 @@ export const DialogProjectCreate = compose(
     withStyles(styles))(
     class DialogProjectCreate extends React.Component<DialogProjectProps & WithStyles<CssRules>> {
         render() {
-            const { classes, open, handleClose, handleSubmit, onSubmit, submitting } = this.props;
+            const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props;
 
             return (
                 <Dialog
@@ -101,7 +103,7 @@ export const DialogProjectCreate = compose(
                                 <Button type="submit"
                                         className={classes.lastButton}
                                         color="primary"
-                                        disabled={submitting}
+                                        disabled={invalid|| submitting || pristine}
                                         variant="contained">
                                     CREATE A PROJECT
                                 </Button>
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 17b0fd7..84845c5 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -184,7 +184,8 @@ interface ProjectPanelDataProps {
 interface ProjectPanelActionProps {
     onItemClick: (item: ProjectPanelItem) => void;
     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
-    onDialogOpen: (ownerUuid: string) => void;
+    onProjectCreationDialogOpen: (ownerUuid: string) => void;
+    onCollectionCreationDialogOpen: (ownerUuid: string) => void;
     onItemDoubleClick: (item: ProjectPanelItem) => void;
     onItemRouteChange: (itemId: string) => void;
 }
@@ -199,7 +200,7 @@ export const ProjectPanel = withStyles(styles)(
                 const { classes } = this.props;
                 return <div>
                     <div className={classes.toolbar}>
-                        <Button color="primary" variant="raised" className={classes.button}>
+                        <Button color="primary" onClick={this.handleNewCollectionClick} variant="raised" className={classes.button}>
                             Create a collection
                         </Button>
                         <Button color="primary" variant="raised" className={classes.button}>
@@ -219,7 +220,11 @@ export const ProjectPanel = withStyles(styles)(
             }
 
             handleNewProjectClick = () => {
-                this.props.onDialogOpen(this.props.currentItemId);
+                this.props.onProjectCreationDialogOpen(this.props.currentItemId);
+            }
+
+            handleNewCollectionClick = () => {
+                this.props.onCollectionCreationDialogOpen(this.props.currentItemId);
             }
             componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: ProjectPanelProps) {
                 if (match.params.id !== currentItemId) {
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 3637528..1f2131a 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -20,6 +20,7 @@ import { sidePanelActions } from '../../store/side-panel/side-panel-action';
 import { SidePanel, SidePanelItem } from '../../components/side-panel/side-panel';
 import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
 import { projectActions } from "../../store/project/project-action";
+import { collectionCreateActions } from '../../store/collections/creator/collection-creator-action';
 import { ProjectPanel } from "../project-panel/project-panel";
 import { DetailsPanel } from '../../views-components/details-panel/details-panel';
 import { ArvadosTheme } from '../../common/custom-theme';
@@ -36,6 +37,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 { CreateCollectionDialog } from '../../views-components/create-collection-dialog/create-collection-dialog';
 
 const drawerWidth = 240;
 const appBarHeight = 100;
@@ -219,6 +221,7 @@ export const Workbench = withStyles(styles)(
                         <ContextMenu />
                         <Snackbar />
                         <CreateProjectDialog />
+                        <CreateCollectionDialog />
                         <CurrentTokenDialog
                             currentToken={this.props.currentToken}
                             open={this.state.isCurrentTokenDialogOpen}
@@ -237,7 +240,8 @@ export const Workbench = withStyles(styles)(
                         kind
                     });
                 }}
-                onDialogOpen={this.handleCreationDialogOpen}
+                onProjectCreationDialogOpen={this.handleProjectCreationDialogOpen}
+                onCollectionCreationDialogOpen={this.handleCollectionCreationDialogOpen}
                 onItemClick={item => {
                     this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
                 }}
@@ -257,7 +261,7 @@ export const Workbench = withStyles(styles)(
                         kind,
                     });
                 }}
-                onDialogOpen={this.handleCreationDialogOpen}
+                onDialogOpen={this.handleProjectCreationDialogOpen}
                 onItemClick={item => {
                     this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
                 }}
@@ -303,10 +307,14 @@ export const Workbench = withStyles(styles)(
                 }
             }
 
-            handleCreationDialogOpen = (itemUuid: string) => {
+            handleProjectCreationDialogOpen = (itemUuid: string) => {
                 this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid }));
             }
 
+            handleCollectionCreationDialogOpen = (itemUuid: string) => {
+                this.props.dispatch(collectionCreateActions.OPEN_COLLECTION_CREATOR({ ownerUuid: itemUuid }));
+            }
+
             openContextMenu = (event: React.MouseEvent<HTMLElement>, resource: { name: string; uuid: string; kind: ContextMenuKind; }) => {
                 event.preventDefault();
                 this.props.dispatch(

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list