[ARVADOS-WORKBENCH2] updated: 1.1.4-293-g0603536

Git user git at public.curoverse.com
Tue Jul 17 03:59:43 EDT 2018


Summary of changes:
 src/common/api/common-resource-service.ts          | 41 ++++++++++++++--------
 src/store/project/project-action.ts                |  2 +-
 src/store/project/project-reducer.ts               |  7 ++--
 src/utils/dialog-validator.tsx                     | 25 ++++++-------
 .../create-project-dialog.tsx                      |  1 +
 .../dialog-create/dialog-project-create.tsx        | 35 ++++++++++++------
 6 files changed, 68 insertions(+), 43 deletions(-)

       via  060353602bb2cb48639f040a3ff986b8dcbe84da (commit)
      from  109103ee47da299c04ffdcabebc38bcd665555d9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 060353602bb2cb48639f040a3ff986b8dcbe84da
Author: Pawel Kowalczyk <pawel.kowalczyk at contractors.roche.com>
Date:   Tue Jul 17 09:59:32 2018 +0200

    validation from BE
    
    Feature #13781
    
    Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk at contractors.roche.com>

diff --git a/src/common/api/common-resource-service.ts b/src/common/api/common-resource-service.ts
index 4c05392..39825c0 100644
--- a/src/common/api/common-resource-service.ts
+++ b/src/common/api/common-resource-service.ts
@@ -5,7 +5,7 @@
 import * as _ from "lodash";
 import FilterBuilder from "./filter-builder";
 import OrderBuilder from "./order-builder";
-import { AxiosInstance } from "axios";
+import { AxiosInstance, AxiosPromise } from "axios";
 import { Resource } from "../../models/resource";
 
 export interface ListArguments {
@@ -26,6 +26,11 @@ export interface ListResults<T> {
     itemsAvailable: number;
 }
 
+export interface Errors {
+    errors: string[];
+    errorToken: string;
+}
+
 export default class CommonResourceService<T extends Resource> {
 
     static mapResponseKeys = (response: any): Promise<any> =>
@@ -49,6 +54,12 @@ export default class CommonResourceService<T extends Resource> {
             }
         }
 
+    static defaultResponse<R>(promise: AxiosPromise<R>): Promise<R> {
+        return promise
+            .then(CommonResourceService.mapResponseKeys)
+            .catch(({ response }) => Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response)));
+    }
+
     protected serverApi: AxiosInstance;
     protected resourceType: string;
 
@@ -58,21 +69,21 @@ export default class CommonResourceService<T extends Resource> {
     }
 
     create(data: Partial<T>) {
-        return this.serverApi
-            .post<T>(this.resourceType, CommonResourceService.mapKeys(_.snakeCase)(data))
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .post<T>(this.resourceType, CommonResourceService.mapKeys(_.snakeCase)(data)));
     }
 
     delete(uuid: string): Promise<T> {
-        return this.serverApi
-            .delete(this.resourceType + uuid)
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .delete(this.resourceType + uuid));
     }
 
     get(uuid: string) {
-        return this.serverApi
-            .get<T>(this.resourceType + uuid)
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .get<T>(this.resourceType + uuid));
     }
 
     list(args: ListArguments = {}): Promise<ListResults<T>> {
@@ -82,11 +93,11 @@ export default class CommonResourceService<T extends Resource> {
             filters: filters ? filters.serialize() : undefined,
             order: order ? order.getOrder() : undefined
         };
-        return this.serverApi
-            .get(this.resourceType, {
-                params: CommonResourceService.mapKeys(_.snakeCase)(params)
-            })
-            .then(CommonResourceService.mapResponseKeys);
+        return CommonResourceService.defaultResponse(
+            this.serverApi
+                .get(this.resourceType, {
+                    params: CommonResourceService.mapKeys(_.snakeCase)(params)
+                }));
     }
 
     update(uuid: string) {
diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts
index 2d59a48..b141736 100644
--- a/src/store/project/project-action.ts
+++ b/src/store/project/project-action.ts
@@ -46,7 +46,7 @@ export const createProject = (project: Partial<ProjectResource>) =>
         return projectService
             .create(projectData)
             .then(project => dispatch(actions.CREATE_PROJECT_SUCCESS(project)))
-            .catch((response) => dispatch(actions.CREATE_PROJECT_ERROR(response.response.data.errors)));
+            .catch(errors => dispatch(actions.CREATE_PROJECT_ERROR(errors.errors.join(''))));
     };
 
 export type ProjectAction = UnionOf<typeof actions>;
diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts
index 10d272c..6df428c 100644
--- a/src/store/project/project-reducer.ts
+++ b/src/store/project/project-reducer.ts
@@ -18,6 +18,7 @@ interface ProjectCreator {
     opened: boolean;
     pending: boolean;
     ownerUuid: string;
+    error?: string;
 }
 
 export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
@@ -113,11 +114,11 @@ const initialState: ProjectState = {
 
 const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
     return actions.match(action, {
-        OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true, pending: false }),
+        OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }),
         CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }),
-        CREATE_PROJECT: () => updateCreator(state, { pending: true }),
+        CREATE_PROJECT: () => updateCreator(state, { pending: true, error: undefined }),
         CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "", pending: false }),
-        CREATE_PROJECT_ERROR: () => updateCreator(state, { ownerUuid: "", pending: false }),
+        CREATE_PROJECT_ERROR: error => updateCreator(state, { pending: false, error }),
         REMOVE_PROJECT: () => state,
         PROJECTS_REQUEST: itemId => {
             const items = _.cloneDeep(state.items);
diff --git a/src/utils/dialog-validator.tsx b/src/utils/dialog-validator.tsx
index 1d1a921..848acec 100644
--- a/src/utils/dialog-validator.tsx
+++ b/src/utils/dialog-validator.tsx
@@ -10,19 +10,15 @@ type ValidatorProps = {
   onChange: (isValid: boolean | string) => void;
   render: (hasError: boolean) => React.ReactElement<any>;
   isRequired: boolean;
+  duplicatedName?: string;
 };
 
 interface ValidatorState {
-  isPatternValid: boolean;
   isLengthValid: boolean;
 }
 
-const nameRegEx = /^[a-zA-Z0-9-_ ]+$/;
-const maxInputLength = 60;
-
 class Validator extends React.Component<ValidatorProps & WithStyles<CssRules>> {
   state: ValidatorState = {
-    isPatternValid: true,
     isLengthValid: true
   };
 
@@ -31,34 +27,35 @@ class Validator extends React.Component<ValidatorProps & WithStyles<CssRules>> {
 
     if (this.props.value !== value) {
       this.setState({
-        isPatternValid: value.match(nameRegEx),
-        isLengthValid: value.length < maxInputLength
+        isLengthValid: value.length < MAX_INPUT_LENGTH
       }, () => this.onChange());
     }
   }
 
   onChange() {
     const { value, onChange, isRequired } = this.props;
-    const { isPatternValid, isLengthValid } = this.state;
-    const isValid = value && isPatternValid && isLengthValid && (isRequired || (!isRequired && value.length > 0));
+    const { isLengthValid } = this.state;
+    const isValid = value && isLengthValid && (isRequired || (!isRequired && value.length > 0));
 
     onChange(isValid);
   }
 
   render() {
-    const { classes, isRequired, value } = this.props;
-    const { isPatternValid, isLengthValid } = this.state;
+    const { classes, isRequired, value, duplicatedName } = this.props;
+    const { isLengthValid } = this.state;
 
     return (
       <span>
-        {this.props.render(!(isPatternValid && isLengthValid) && (isRequired || (!isRequired && value.length > 0)))}
-        {!isPatternValid && (isRequired || (!isRequired && value.length > 0)) ? <span className={classes.formInputError}>This field allow only alphanumeric characters, dashes, spaces and underscores.<br /></span> : null}
-        {!isLengthValid ? <span className={classes.formInputError}>This field should have max 60 characters.</span> : null}
+        {this.props.render(!isLengthValid && (isRequired || (!isRequired && value.length > 0)))}
+        {!isLengthValid ? <span className={classes.formInputError}>This field should have max 255 characters.</span> : null}
+        {duplicatedName ? <span className={classes.formInputError}>Project with this name already exists</span> : null}
       </span>
     );
   }
 }
 
+const MAX_INPUT_LENGTH = 255;
+
 type CssRules = "formInputError";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
diff --git a/src/views-components/create-project-dialog/create-project-dialog.tsx b/src/views-components/create-project-dialog/create-project-dialog.tsx
index eb69837..2cdd479 100644
--- a/src/views-components/create-project-dialog/create-project-dialog.tsx
+++ b/src/views-components/create-project-dialog/create-project-dialog.tsx
@@ -13,6 +13,7 @@ import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
 const mapStateToProps = (state: RootState) => ({
     open: state.projects.creator.opened,
     pending: state.projects.creator.pending,
+    error: state.projects.creator.error
 });
 
 const submit = (data: { name: string, description: string }) =>
diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx
index 48b8911..9f4768a 100644
--- a/src/views-components/dialog-create/dialog-project-create.tsx
+++ b/src/views-components/dialog-create/dialog-project-create.tsx
@@ -8,13 +8,14 @@ import Dialog from '@material-ui/core/Dialog';
 import DialogActions from '@material-ui/core/DialogActions';
 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 { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress, Typography } from '@material-ui/core';
 
 import Validator from '../../utils/dialog-validator';
 
 interface ProjectCreateProps {
   open: boolean;
   pending: boolean;
+  error: string;
   handleClose: () => void;
   onSubmit: (data: { name: string, description: string }) => void;
 }
@@ -24,6 +25,7 @@ interface DialogState {
   description: string;
   isNameValid: boolean;
   isDescriptionValid: boolean;
+  duplicatedName: string;
 }
 
 class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyles<CssRules>> {
@@ -31,11 +33,22 @@ class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyle
     name: '',
     description: '',
     isNameValid: false,
-    isDescriptionValid: true
+    isDescriptionValid: true,
+    duplicatedName: ''
   };
 
+  componentWillReceiveProps(nextProps: ProjectCreateProps) {
+    const { error } = nextProps;
+
+    if (this.props.error !== error) {
+      this.setState({
+        duplicatedName: error
+      });
+    }
+  }
+
   render() {
-    const { name, description, isNameValid, isDescriptionValid } = this.state;
+    const { name, description, isNameValid, isDescriptionValid, duplicatedName } = this.state;
     const { classes, open, handleClose, pending } = this.props;
 
     return (
@@ -49,6 +62,7 @@ class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyle
               value={name}
               onChange={e => this.isNameValid(e)}
               isRequired={true}
+              duplicatedName={duplicatedName}
               render={hasError =>
                 <TextField
                   margin="dense"
@@ -56,7 +70,7 @@ class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyle
                   id="name"
                   onChange={e => this.handleProjectName(e)}
                   label="Project name"
-                  error={hasError}
+                  error={hasError || !!duplicatedName}
                   fullWidth />} />
             <Validator
               value={description}
@@ -74,14 +88,14 @@ class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyle
           </DialogContent>
           <DialogActions>
             <Button onClick={handleClose} className={classes.button} color="primary" disabled={pending}>CANCEL</Button>
-            <Button onClick={this.handleSubmit} 
-              className={classes.lastButton} 
-              color="primary" 
-              disabled={!isNameValid || (!isDescriptionValid && description.length > 0) || pending} 
+            <Button onClick={this.handleSubmit}
+              className={classes.lastButton}
+              color="primary"
+              disabled={!isNameValid || (!isDescriptionValid && description.length > 0) || pending}
               variant="contained">
               CREATE A PROJECT
-              </Button>
-              {pending && <CircularProgress size={20} className={classes.createProgress} />}
+            </Button>
+            {pending && <CircularProgress size={20} className={classes.createProgress} />}
           </DialogActions>
         </div>
       </Dialog>
@@ -98,6 +112,7 @@ class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyle
   handleProjectName(e: any) {
     this.setState({
       name: e.target.value,
+      duplicatedName: ''
     });
   }
 

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list