18979: Disable vm login form submit and warn with unsaved group input
authorStephen Smith <stephen@curii.com>
Thu, 11 Aug 2022 21:57:28 +0000 (17:57 -0400)
committerStephen Smith <stephen@curii.com>
Fri, 12 Aug 2022 01:57:28 +0000 (21:57 -0400)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

src/components/chips-input/chips-input.tsx
src/views-components/virtual-machines-dialog/add-login-dialog.tsx
src/views-components/virtual-machines-dialog/group-array-input.tsx

index cbb1fb1283b31246152f2c32b6a961cb5f256248..7b9ff4a6a81e4fe199301004d1905e6b0f664826 100644 (file)
@@ -12,6 +12,7 @@ interface ChipsInputProps<Value> {
     values: Value[];
     getLabel?: (value: Value) => string;
     onChange: (value: Value[]) => void;
+    onPartialInput?: (value: boolean) => void;
     handleFocus?: (e: any) => void;
     handleBlur?: (e: any) => void;
     chipsClassName?: string;
@@ -54,6 +55,9 @@ export const ChipsInput = withStyles(styles)(
 
         setText = (event: React.ChangeEvent<HTMLInputElement>) => {
             this.setState({ text: event.target.value }, () => {
+                // Update partial input status
+                this.props.onPartialInput && this.props.onPartialInput(this.state.text !== '');
+
                 // If pattern is provided, check for delimiter
                 if (this.props.pattern) {
                     const matches = this.state.text.match(this.props.pattern);
@@ -92,6 +96,7 @@ export const ChipsInput = withStyles(styles)(
                     this.setState({ text: '' });
                     this.props.onChange([...this.props.values, newValue]);
                 }
+                this.props.onPartialInput && this.props.onPartialInput(false);
             }
         }
 
index 1654452bf493b656b031c648fa1faf96a1275e1a..b591bb8fcebbeb24b832db6dff40756a1ebe36b3 100644 (file)
@@ -9,7 +9,7 @@ import { withDialog, WithDialogProps } from "store/dialog/with-dialog";
 import { FormDialog } from 'components/form-dialog/form-dialog';
 import { VIRTUAL_MACHINE_ADD_LOGIN_DIALOG, VIRTUAL_MACHINE_ADD_LOGIN_FORM, addUpdateVirtualMachineLogin, AddLoginFormData, VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD } from 'store/virtual-machines/virtual-machines-actions';
 import { ParticipantSelect } from 'views-components/sharing-dialog/participant-select';
-import { GroupArrayInput } from 'views-components/virtual-machines-dialog/group-array-input';
+import { GroupArrayInput, GroupArrayDataProps } from 'views-components/virtual-machines-dialog/group-array-input';
 
 export const VirtualMachineAddLoginDialog = compose(
     withDialog(VIRTUAL_MACHINE_ADD_LOGIN_DIALOG),
@@ -20,16 +20,25 @@ export const VirtualMachineAddLoginDialog = compose(
         }
     })
 )(
-    (props: CreateGroupDialogComponentProps) =>
-        <FormDialog
+    (props: CreateGroupDialogComponentProps) => {
+        const [hasPartialGroupInput, setPartialGroupInput] = React.useState<boolean>(false);
+
+        return <FormDialog
             dialogTitle={props.data.updating ? "Update login permission" : "Add login permission"}
             formFields={AddLoginFormFields}
             submitLabel={props.data.updating ? "Update" : "Add"}
             {...props}
-        />
+            data={{
+                ...props.data,
+                setPartialGroupInput,
+                hasPartialGroupInput,
+            }}
+            invalid={props.invalid || hasPartialGroupInput}
+        />;
+    }
 );
 
-type CreateGroupDialogComponentProps = WithDialogProps<{updating: boolean}> & InjectedFormProps<AddLoginFormData>;
+type CreateGroupDialogComponentProps = WithDialogProps<{updating: boolean}> & GroupArrayDataProps & InjectedFormProps<AddLoginFormData>;
 
 const AddLoginFormFields = (props) => {
     return <>
@@ -42,6 +51,8 @@ const AddLoginFormFields = (props) => {
             name={VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD}
             input={{id:"Add groups to VM login (eg: docker, sudo)", disabled:false}}
             required={false}
+            setPartialGroupInput={props.data.setPartialGroupInput}
+            hasPartialGroupInput={props.data.hasPartialGroupInput}
         />
     </>;
 }
index 3ea5e77c883e2316f2d2fc7dd16a40dfd7953558..95a86420795b36d22142b4277cd0d4387077ffd3 100644 (file)
@@ -4,34 +4,59 @@
 
 import React from 'react';
 import { StringArrayCommandInputParameter } from 'models/workflow';
-import { Field } from 'redux-form';
+import { Field, GenericField } from 'redux-form';
 import { GenericInputProps } from 'views/run-process-panel/inputs/generic-input';
 import { ChipsInput } from 'components/chips-input/chips-input';
 import { identity } from 'lodash';
-import { withStyles, WithStyles, FormGroup, Input, InputLabel, FormControl } from '@material-ui/core';
+import { withStyles, WithStyles, FormGroup, Input, InputLabel, FormControl, FormHelperText } from '@material-ui/core';
+import classnames from "classnames";
+import { ArvadosTheme } from 'common/custom-theme';
 
-export interface StringArrayInputProps {
+export interface GroupArrayDataProps {
+  hasPartialGroupInput?: boolean;
+  setPartialGroupInput?: (value: boolean) => void;
+}
+
+interface GroupArrayFieldProps {
+  commandInput: StringArrayCommandInputParameter;
+}
+
+const GroupArrayField = Field as new () => GenericField<GroupArrayDataProps & GroupArrayFieldProps>;
+
+export interface GroupArrayInputProps {
   name: string;
   input: StringArrayCommandInputParameter;
   required: boolean;
 }
 
-type CssRules = 'chips';
+type CssRules = 'chips' | 'partialInputHelper' | 'partialInputHelperVisible';
 
-const styles = {
+const styles = (theme: ArvadosTheme) => ({
     chips: {
         marginTop: "16px",
     },
-};
+    partialInputHelper: {
+        textAlign: 'right' as 'right',
+        visibility: 'hidden' as 'hidden',
+        color: theme.palette.error.dark,
+    },
+    partialInputHelperVisible: {
+        visibility: 'visible' as 'visible',
+    }
+});
 
-export const GroupArrayInput = ({name, input}: StringArrayInputProps) =>
-    <Field
-        name={name}
-        commandInput={input}
-        component={StringArrayInputComponent as any}
-        />;
+export const GroupArrayInput = ({name, input, setPartialGroupInput, hasPartialGroupInput}: GroupArrayInputProps & GroupArrayDataProps) => {
+  console.log(hasPartialGroupInput);
+  return <GroupArrayField
+      name={name}
+      commandInput={input}
+      component={GroupArrayInputComponent as any}
+      setPartialGroupInput={setPartialGroupInput}
+      hasPartialGroupInput={hasPartialGroupInput}
+      />;
+}
 
-const StringArrayInputComponent = (props: GenericInputProps) => {
+const GroupArrayInputComponent = (props: GenericInputProps & GroupArrayDataProps) => {
   return <FormGroup>
         <FormControl fullWidth error={props.meta.error}>
           <InputLabel shrink={props.meta.active || props.input.value.length > 0}>{props.commandInput.id}</InputLabel>
@@ -41,24 +66,30 @@ const StringArrayInputComponent = (props: GenericInputProps) => {
     };
 
 const StyledInputComponent = withStyles(styles)(
-  class InputComponent extends React.PureComponent<GenericInputProps & WithStyles<CssRules>>{
+  class InputComponent extends React.PureComponent<GenericInputProps & WithStyles<CssRules> & GroupArrayDataProps>{
       render() {
           const { classes } = this.props;
-          const { commandInput, input, meta } = this.props;
-          return <ChipsInput
-              deletable={!commandInput.disabled}
-              orderable={!commandInput.disabled}
-              disabled={commandInput.disabled}
-              values={input.value}
-              onChange={this.handleChange}
-              handleFocus={input.onFocus}
-              createNewValue={identity}
-              inputComponent={Input}
-              chipsClassName={classes.chips}
-              pattern={/[_a-z][-0-9_a-z]*/ig}
-              inputProps={{
-                  error: meta.error,
-              }} />;
+          const { commandInput, input, meta, hasPartialGroupInput } = this.props;
+          return <>
+            <ChipsInput
+                deletable={!commandInput.disabled}
+                orderable={!commandInput.disabled}
+                disabled={commandInput.disabled}
+                values={input.value}
+                onChange={this.handleChange}
+                handleFocus={input.onFocus}
+                createNewValue={identity}
+                inputComponent={Input}
+                chipsClassName={classes.chips}
+                pattern={/[_a-z][-0-9_a-z]*/ig}
+                onPartialInput={this.props.setPartialGroupInput}
+                inputProps={{
+                    error: meta.error || hasPartialGroupInput,
+                }} />
+                <FormHelperText className={classnames([classes.partialInputHelper, ...(hasPartialGroupInput ? [classes.partialInputHelperVisible] : [])])}>
+                  Press enter to complete group name
+                </FormHelperText>
+          </>;
       }
 
       handleChange = (values: {}[]) => {