mirror of https://github.com/apache/druid.git
Fix required field treatment (#11228)
This commit is contained in:
parent
8e5048e643
commit
d11be88c4b
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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{' '}
|
||||
|
|
|
@ -107,7 +107,6 @@ export const TIMESTAMP_SPEC_FIELDS: Field<TimestampSpec>[] = [
|
|||
name: 'column',
|
||||
type: 'string',
|
||||
defaultValue: 'timestamp',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'format',
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue