Fix required field treatment (#11228)

This commit is contained in:
Vadim Ogievetsky 2021-05-11 17:58:37 -07:00 committed by GitHub
parent 8e5048e643
commit d11be88c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 68 deletions

View File

@ -5,8 +5,8 @@ exports[`AutoForm matches snapshot 1`] = `
className="auto-form"
>
<Memo(FormGroupWithInfo)
key="testOne"
label="Test one"
key="testNumber"
label="Test number"
>
<Memo(NumericInputWithDefault)
disabled={false}
@ -18,8 +18,8 @@ exports[`AutoForm matches snapshot 1`] = `
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testTwo"
label="Test two"
key="testSizeBytes"
label="Test size bytes"
>
<Blueprint3.NumericInput
allowNumericCharactersOnly={true}
@ -40,8 +40,8 @@ exports[`AutoForm matches snapshot 1`] = `
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testThree"
label="Test three"
key="testString"
label="Test string"
>
<Memo(SuggestibleInput)
disabled={false}
@ -52,8 +52,20 @@ exports[`AutoForm matches snapshot 1`] = `
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testFour"
label="Test four"
key="testStringWithDefault"
label="Test string with default"
>
<Memo(SuggestibleInput)
disabled={false}
onBlur={[Function]}
onValueChange={[Function]}
placeholder=""
value="Hello World"
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testBoolean"
label="Test boolean"
>
<Blueprint3.ButtonGroup>
<Blueprint3.Button
@ -73,8 +85,8 @@ exports[`AutoForm matches snapshot 1`] = `
</Blueprint3.ButtonGroup>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testFourWithDefault"
label="Test four with default"
key="testBooleanWithDefault"
label="Test boolean with default"
>
<Blueprint3.ButtonGroup>
<Blueprint3.Button
@ -94,8 +106,8 @@ exports[`AutoForm matches snapshot 1`] = `
</Blueprint3.ButtonGroup>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testFive"
label="Test five"
key="testStringArray"
label="Test string array"
>
<Memo(ArrayInput)
disabled={false}
@ -105,8 +117,24 @@ exports[`AutoForm matches snapshot 1`] = `
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testSix"
label="Test six"
key="testStringArrayWithDefault"
label="Test string array with default"
>
<Memo(ArrayInput)
disabled={false}
onChange={[Function]}
placeholder=""
values={
Array [
"Hello",
"World",
]
}
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testJson"
label="Test json"
>
<Memo(JsonInput)
onChange={[Function]}
@ -114,12 +142,16 @@ exports[`AutoForm matches snapshot 1`] = `
/>
</Memo(FormGroupWithInfo)>
<Memo(FormGroupWithInfo)
key="testSeven"
label="Test seven"
key="testStringRequiredAndDefaultValue"
label="Test string required and default value"
>
<Memo(JsonInput)
onChange={[Function]}
<Memo(SuggestibleInput)
disabled={false}
intent="primary"
onBlur={[Function]}
onValueChange={[Function]}
placeholder=""
value=""
/>
</Memo(FormGroupWithInfo)>
<Blueprint3.FormGroup

View File

@ -28,14 +28,26 @@ describe('AutoForm', () => {
const autoForm = shallow(
<AutoForm
fields={[
{ name: 'testOne', type: 'number' },
{ name: 'testTwo', type: 'size-bytes' },
{ name: 'testThree', type: 'string' },
{ name: 'testFour', type: 'boolean' },
{ name: 'testFourWithDefault', type: 'boolean', defaultValue: false },
{ name: 'testFive', type: 'string-array' },
{ name: 'testSix', type: 'json' },
{ name: 'testSeven', type: 'json' },
{ name: 'testNumber', type: 'number' },
{ name: 'testSizeBytes', type: 'size-bytes' },
{ name: 'testString', type: 'string' },
{ name: 'testStringWithDefault', type: 'string', defaultValue: 'Hello World' },
{ name: 'testBoolean', type: 'boolean' },
{ name: 'testBooleanWithDefault', type: 'boolean', defaultValue: false },
{ name: 'testStringArray', type: 'string-array' },
{
name: 'testStringArrayWithDefault',
type: 'string-array',
defaultValue: ['Hello', 'World'],
},
{ name: 'testJson', type: 'json' },
{
name: 'testStringRequiredAndDefaultValue',
type: 'string',
defaultValue: 'hello',
required: () => true,
},
{ name: 'testNotDefined', type: 'string', defined: false },
{ name: 'testAdvanced', type: 'string', hideInMore: true },

View File

@ -62,6 +62,12 @@ export interface Field<M> {
issueWithValue?: (value: any) => string | undefined;
}
interface ComputedFieldValues {
required: boolean;
defaultValue?: any;
modelValue: any;
}
export interface AutoFormProps<M> {
fields: Field<M>[];
model: M | undefined;
@ -93,6 +99,15 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
return newLabel;
}
static computeFieldValues<M>(model: M | undefined, field: Field<M>): ComputedFieldValues {
const required = AutoForm.evaluateFunctor(field.required, model, false);
return {
required,
defaultValue: required ? undefined : field.defaultValue,
modelValue: deepGet(model as any, field.name),
};
}
static evaluateFunctor<M, R>(
functor: undefined | Functor<M, R>,
model: M | undefined,
@ -204,12 +219,12 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
private renderNumberInput(field: Field<T>): JSX.Element {
const { model, large, onFinalize } = this.props;
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
const modelValue = deepGet(model as any, field.name);
return (
<NumericInputWithDefault
value={modelValue}
defaultValue={field.defaultValue}
defaultValue={defaultValue}
onValueChange={(valueAsNumber: number, valueAsString: string) => {
let newValue: number | undefined;
if (valueAsString !== '' && !isNaN(valueAsNumber)) {
@ -228,21 +243,18 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
intent={
AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null
? AutoForm.REQUIRED_INTENT
: undefined
}
intent={required && modelValue == null ? AutoForm.REQUIRED_INTENT : undefined}
/>
);
}
private renderSizeBytesInput(field: Field<T>): JSX.Element {
const { model, large, onFinalize } = this.props;
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
return (
<NumericInput
value={deepGet(model as any, field.name) || field.defaultValue}
value={modelValue || defaultValue}
onValueChange={(v: number) => {
if (isNaN(v)) return;
this.fieldChange(field, v);
@ -256,17 +268,18 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
fill
large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
intent={required && modelValue == null ? AutoForm.REQUIRED_INTENT : undefined}
/>
);
}
private renderStringInput(field: Field<T>, sanitize?: (str: string) => string): JSX.Element {
const { model, large, onFinalize } = this.props;
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
const modelValue = deepGet(model as any, field.name);
return (
<SuggestibleInput
value={modelValue != null ? modelValue : field.defaultValue || ''}
value={modelValue != null ? modelValue : defaultValue || ''}
onValueChange={v => {
if (sanitize && typeof v === 'string') v = sanitize(v);
this.fieldChange(field, v);
@ -279,24 +292,17 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
suggestions={AutoForm.evaluateFunctor(field.suggestions, model, undefined)}
large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
intent={
AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null
? AutoForm.REQUIRED_INTENT
: undefined
}
intent={required && modelValue == null ? AutoForm.REQUIRED_INTENT : undefined}
/>
);
}
private renderBooleanInput(field: Field<T>): JSX.Element {
const { model, large, onFinalize } = this.props;
const modelValue = deepGet(model as any, field.name);
const shownValue = modelValue == null ? field.defaultValue : modelValue;
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
const shownValue = modelValue == null ? defaultValue : modelValue;
const disabled = AutoForm.evaluateFunctor(field.disabled, model, false);
const intent =
AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null
? AutoForm.REQUIRED_INTENT
: undefined;
const intent = required && modelValue == null ? AutoForm.REQUIRED_INTENT : undefined;
return (
<ButtonGroup large={large}>
@ -342,41 +348,34 @@ export class AutoForm<T extends Record<string, any>> extends React.PureComponent
private renderStringArrayInput(field: Field<T>): JSX.Element {
const { model, large } = this.props;
const modelValue = deepGet(model as any, field.name);
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
return (
<ArrayInput
values={modelValue || []}
values={modelValue || defaultValue || []}
onChange={(v: any) => {
this.fieldChange(field, v);
}}
placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
large={large}
disabled={AutoForm.evaluateFunctor(field.disabled, model, false)}
intent={
AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null
? AutoForm.REQUIRED_INTENT
: undefined
}
intent={required && modelValue == null ? AutoForm.REQUIRED_INTENT : undefined}
/>
);
}
private renderIntervalInput(field: Field<T>): JSX.Element {
const { model } = this.props;
const { required, defaultValue, modelValue } = AutoForm.computeFieldValues(model, field);
const modelValue = deepGet(model as any, field.name);
return (
<IntervalInput
interval={modelValue != null ? modelValue : field.defaultValue || ''}
interval={modelValue != null ? modelValue : defaultValue || ''}
onValueChange={(v: any) => {
this.fieldChange(field, v);
}}
placeholder={AutoForm.evaluateFunctor(field.placeholder, model, '')}
intent={
AutoForm.evaluateFunctor(field.required, model, false) && modelValue == null
? AutoForm.REQUIRED_INTENT
: undefined
}
intent={required && modelValue == null ? AutoForm.REQUIRED_INTENT : undefined}
/>
);
}

View File

@ -883,7 +883,6 @@ export function getIoConfigFormFields(ingestionComboType: IngestionComboType): F
'kinesis.us-gov-east-1.amazonaws.com',
'kinesis.us-gov-west-1.amazonaws.com',
],
required: true,
info: (
<>
The Amazon Kinesis stream endpoint for a region. You can find a list of endpoints{' '}

View File

@ -107,7 +107,6 @@ export const TIMESTAMP_SPEC_FIELDS: Field<TimestampSpec>[] = [
name: 'column',
type: 'string',
defaultValue: 'timestamp',
required: true,
},
{
name: 'format',

View File

@ -712,7 +712,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
comboType: IngestionComboTypeWithExtra,
disabled?: boolean,
): JSX.Element | undefined {
const { overlordModules, selectedComboType } = this.state;
const { overlordModules, selectedComboType, spec } = this.state;
if (!overlordModules) return;
const requiredModule = getRequiredModule(comboType);
const goodToGo = !disabled && (!requiredModule || overlordModules.includes(requiredModule));
@ -722,10 +722,15 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
className={classNames({ disabled: !goodToGo, active: selectedComboType === comboType })}
interactive
elevation={1}
onClick={() => {
this.setState({
selectedComboType: selectedComboType !== comboType ? comboType : undefined,
});
onClick={e => {
if (e.altKey && e.shiftKey) {
this.updateSpec(updateIngestionType(spec, comboType as any));
this.updateStep('connect');
} else {
this.setState({
selectedComboType: selectedComboType !== comboType ? comboType : undefined,
});
}
}}
>
<img