[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