Skip to content

Commit

Permalink
fix: disable launch button and show validation messaages
Browse files Browse the repository at this point in the history
Signed-off-by: James <[email protected]>
  • Loading branch information
james-union committed Aug 3, 2022
1 parent bcdd464 commit 431d485
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ export interface LaunchFormActionsProps {
state: BaseInterpretedLaunchState;
service: BaseLaunchService;
onClose(): void;
isError: boolean;
}
/** Renders the Submit/Cancel buttons for a LaunchForm */
export const LaunchFormActions: React.FC<LaunchFormActionsProps> = ({
state,
service,
onClose,
isError,
}) => {
const styles = useStyles();
const submissionInFlight = state.matches(LaunchState.SUBMITTING);
Expand Down Expand Up @@ -68,7 +70,7 @@ export const LaunchFormActions: React.FC<LaunchFormActionsProps> = ({
</Button>
<Button
color="primary"
disabled={!canSubmit}
disabled={!canSubmit || isError}
id="launch-workflow-submit"
onClick={submit}
type="submit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,68 @@ import { SimpleInput } from './SimpleInput';
import { StructInput } from './StructInput';
import { UnionInput } from './UnionInput';
import { useStyles } from './styles';
import { BaseInterpretedLaunchState, InputProps, InputType, LaunchFormInputsRef } from './types';
import {
BaseInterpretedLaunchState,
InputProps,
InputType,
InputValue,
LaunchFormInputsRef,
} from './types';
import { UnsupportedInput } from './UnsupportedInput';
import { UnsupportedRequiredInputsError } from './UnsupportedRequiredInputsError';
import { useFormInputsState } from './useFormInputsState';
import { isEnterInputsState } from './utils';
import { getHelperForInput } from './inputHelpers/getHelperForInput';

export function getComponentForInput(
input: InputProps,
showErrors: boolean,
setIsError: (boolean) => void,
) {
const props = { ...input, error: showErrors ? input.error : undefined, setIsError };

export function getComponentForInput(input: InputProps, showErrors: boolean) {
const props = { ...input, error: showErrors ? input.error : undefined };
const onChange = (newValue: InputValue) => {
const helper = getHelperForInput(input.typeDefinition.type);
try {
helper.validate({ ...input, value: newValue });
setIsError(false);
} catch (e) {
setIsError(true);
}
input.onChange(newValue);
};

switch (input.typeDefinition.type) {
case InputType.Union:
return <UnionInput {...props} />;
return <UnionInput {...props} onChange={onChange} />;
case InputType.Blob:
return <BlobInput {...props} />;
return <BlobInput {...props} onChange={onChange} />;
case InputType.Collection:
return <CollectionInput {...props} />;
return <CollectionInput {...props} onChange={onChange} />;
case InputType.Struct:
return <StructInput {...props} />;
return <StructInput {...props} onChange={onChange} />;
case InputType.Map:
return <MapInput {...props} />;
return <MapInput {...props} onChange={onChange} />;
case InputType.Unknown:
case InputType.None:
return <UnsupportedInput {...props} />;
return <UnsupportedInput {...props} onChange={onChange} />;
default:
return <SimpleInput {...props} />;
return <SimpleInput {...props} onChange={onChange} />;
}
}

export interface LaunchFormInputsProps {
state: BaseInterpretedLaunchState;
variant: 'workflow' | 'task';
setIsError: (boolean) => void;
}

const RenderFormInputs: React.FC<{
inputs: InputProps[];
showErrors: boolean;
variant: LaunchFormInputsProps['variant'];
}> = ({ inputs, showErrors, variant }) => {
setIsError: (boolean) => void;
}> = ({ inputs, showErrors, variant, setIsError }) => {
const styles = useStyles();
return inputs.length === 0 ? (
<NoInputsNeeded variant={variant} />
Expand All @@ -59,7 +83,7 @@ const RenderFormInputs: React.FC<{
</header>
{inputs.map((input) => (
<div key={input.label} className={styles.formControl}>
{getComponentForInput(input, showErrors)}
{getComponentForInput(input, showErrors, setIsError)}
</div>
))}
</>
Expand All @@ -69,7 +93,7 @@ const RenderFormInputs: React.FC<{
export const LaunchFormInputsImpl: React.RefForwardingComponent<
LaunchFormInputsRef,
LaunchFormInputsProps
> = ({ state, variant }, ref) => {
> = ({ state, variant, setIsError }, ref) => {
const { parsedInputs, unsupportedRequiredInputs, showErrors } = state.context;
const { getValues, inputs, validate } = useFormInputsState(parsedInputs);
React.useImperativeHandle(ref, () => ({
Expand All @@ -82,7 +106,12 @@ export const LaunchFormInputsImpl: React.RefForwardingComponent<
{state.matches(LaunchState.UNSUPPORTED_INPUTS) ? (
<UnsupportedRequiredInputsError inputs={unsupportedRequiredInputs} variant={variant} />
) : (
<RenderFormInputs inputs={inputs} showErrors={showErrors} variant={variant} />
<RenderFormInputs
inputs={inputs}
showErrors={showErrors}
variant={variant}
setIsError={setIsError}
/>
)}
</section>
) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const LaunchTaskForm: React.FC<LaunchTaskFormProps> = (props) => {
const styles = useStyles();
const baseState = state as BaseInterpretedLaunchState;
const baseService = service as BaseLaunchService;
const [isError, setIsError] = React.useState<boolean>(false);

// Any time the inputs change (even if it's just re-ordering), we must
// change the form key so that the inputs component will re-mount.
Expand Down Expand Up @@ -69,13 +70,24 @@ export const LaunchTaskForm: React.FC<LaunchTaskFormProps> = (props) => {
showErrors={state.context.showErrors}
/>
) : null}
<LaunchFormInputs key={formKey} ref={formInputsRef} state={baseState} variant="task" />
<LaunchFormInputs
key={formKey}
ref={formInputsRef}
state={baseState}
variant="task"
setIsError={setIsError}
/>
<LaunchInterruptibleInput
initialValue={state.context.interruptible}
ref={interruptibleInputRef}
/>
</DialogContent>
<LaunchFormActions state={baseState} service={baseService} onClose={props.onClose} />
<LaunchFormActions
state={baseState}
service={baseService}
onClose={props.onClose}
isError={isError}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const LaunchWorkflowForm: React.FC<LaunchWorkflowFormProps> = (props) =>
const styles = useStyles();
const baseState = state as BaseInterpretedLaunchState;
const baseService = service as BaseLaunchService;
const [isError, setIsError] = React.useState<boolean>(false);

// Any time the inputs change (even if it's just re-ordering), we must
// change the form key so that the inputs component will re-mount.
Expand Down Expand Up @@ -94,7 +95,13 @@ export const LaunchWorkflowForm: React.FC<LaunchWorkflowFormProps> = (props) =>
/>
</section>
) : null}
<LaunchFormInputs key={formKey} ref={formInputsRef} state={baseState} variant="workflow" />
<LaunchFormInputs
key={formKey}
ref={formInputsRef}
state={baseState}
variant="workflow"
setIsError={setIsError}
/>
<Accordion className={styles.noBorder}>
<AccordionSummary
classes={{
Expand All @@ -120,7 +127,12 @@ export const LaunchWorkflowForm: React.FC<LaunchWorkflowFormProps> = (props) =>
</AccordionDetails>
</Accordion>
</DialogContent>
<LaunchFormActions state={baseState} service={baseService} onClose={props.onClose} />
<LaunchFormActions
state={baseState}
service={baseService}
onClose={props.onClose}
isError={isError}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, IconButton, TextField, Typography } from '@material-ui/core';
import { Button, FormHelperText, IconButton, TextField, Typography } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import * as React from 'react';
import RemoveIcon from '@material-ui/icons/Remove';
Expand All @@ -7,6 +7,7 @@ import CardContent from '@material-ui/core/CardContent';
import { requiredInputSuffix } from './constants';
import { InputProps, InputType, InputTypeDefinition, InputValue } from './types';
import { formatType, toMappedTypeValue } from './utils';
import { getHelperForInput } from './inputHelpers/getHelperForInput';

const useStyles = makeStyles((theme: Theme) => ({
formControl: {
Expand Down Expand Up @@ -49,9 +50,26 @@ const MapSingleInputItem = (props: MapInputItemProps) => {
const classes = useStyles();
const { data, subtype, setKey, setValue, isValid, onDeleteItem } = props;
const [error, setError] = React.useState(false);
const [focused, setFocused] = React.useState(false);
const [touched, setTouched] = React.useState(false);

const isOneLineType = subtype?.type === InputType.String || subtype?.type === InputType.Integer;

let invalidValueError = null;
if (subtype && !focused && touched) {
const helper = getHelperForInput(subtype.type);
try {
helper.validate({
name: data.key,
value: data.value,
required: true,
typeDefinition: subtype,
});
} catch (e) {
invalidValueError = e?.message;
}
}

return (
<div className={classes.controls}>
<TextField
Expand All @@ -70,13 +88,18 @@ const MapSingleInputItem = (props: MapInputItemProps) => {
<TextField
label={subtype ? `${formatType(subtype)}${requiredInputSuffix}` : ''}
onChange={({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
setTouched(true);
setValue(value);
}}
value={data.value}
variant="outlined"
className={classes.valueControl}
multiline={!isOneLineType}
type={subtype?.type === InputType.Integer ? 'number' : 'text'}
error={!!invalidValueError}
helperText={invalidValueError ? invalidValueError : ''}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
/>
<IconButton onClick={onDeleteItem}>
<RemoveIcon />
Expand Down Expand Up @@ -117,17 +140,30 @@ export const MapInput = (props: InputProps) => {
value,
label,
onChange,
error,
typeDefinition: { subtype },
setIsError,
} = props;
const classes = useStyles();

console.log('MY FILTER: org error: ', error);

const [data, setData] = React.useState<MapInputItem[]>(parseMappedTypeValue(value));

const onAddItem = () => {
setIsError?.(true);
setData((data) => [...data, getNewMapItem(data.length)]);
};

const updateUpperStream = () => {
let newError = false;
data.forEach((item) => {
if (item.id === null || !item.key?.length || !item.value?.length) newError = true;
else {
if (data.findIndex(({ key, id }) => id !== item.id && key === item.key) >= 0)
newError = true;
}
});
const newPairs = data
.filter((item) => {
// we filter out delted values and items with errors or empty keys/values
Expand All @@ -141,6 +177,7 @@ export const MapInput = (props: InputProps) => {
});
const newValue = toMappedTypeValue(newPairs);
onChange(newValue);
if (newError) setIsError?.(newError);
};

const onSetKey = (id: number | null, key: string) => {
Expand All @@ -166,7 +203,7 @@ export const MapInput = (props: InputProps) => {
setData((data) => {
const dataIndex = data.findIndex((item) => item.id === id);
if (dataIndex >= 0 && dataIndex < data.length) {
data[dataIndex].id = null;
return [...data.splice(0, dataIndex), ...data.splice(dataIndex + 1)];
}
return [...data];
});
Expand Down Expand Up @@ -204,6 +241,7 @@ export const MapInput = (props: InputProps) => {
/>
);
})}
{error && <FormHelperText error={true}>{error}</FormHelperText>}
<div className={classes.addButton}>
<Button onClick={onAddItem}>+ ADD ITEM</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ const generateListOfSearchableSelectorOptions = (
};

export const UnionInput = (props: InputProps) => {
const { initialValue, required, label, onChange, typeDefinition, error, description } = props;
const {
initialValue,
required,
label,
onChange,
typeDefinition,
error,
description,
setIsError,
} = props;

const classes = useStyles();

Expand Down Expand Up @@ -149,6 +158,7 @@ export const UnionInput = (props: InputProps) => {
error: error,
} as InputProps,
true,
setIsError,
)}
</div>
</CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export interface InputProps {
typeDefinition: InputTypeDefinition;
value?: InputValue;
onChange: InputChangeHandler;
setIsError: (boolean) => void;
}

export interface ParsedInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function useFormInputState(parsedInput: ParsedInput): FormInputState {
validate,
value,
helperText: parsedInput.description,
setIsError: () => {},
};
}

Expand Down

0 comments on commit 431d485

Please sign in to comment.