-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support Struct as a Launch input type (#113)
* test: checkpoint for adding test cases * test: fixing all the broken test cases * feat: add input to support entering structs * chore: cleanup
- Loading branch information
Showing
11 changed files
with
402 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { TextField } from '@material-ui/core'; | ||
import * as React from 'react'; | ||
import { InputChangeHandler, InputProps } from './types'; | ||
import { getLaunchInputId } from './utils'; | ||
|
||
function stringChangeHandler(onChange: InputChangeHandler) { | ||
return ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => { | ||
onChange(value); | ||
}; | ||
} | ||
|
||
/** Handles rendering of the input component for a Struct */ | ||
export const StructInput: React.FC<InputProps> = props => { | ||
const { | ||
error, | ||
label, | ||
name, | ||
onChange, | ||
typeDefinition: { subtype }, | ||
value = '' | ||
} = props; | ||
const hasError = !!error; | ||
const helperText = hasError ? error : props.helperText; | ||
return ( | ||
<TextField | ||
id={getLaunchInputId(name)} | ||
error={hasError} | ||
helperText={helperText} | ||
fullWidth={true} | ||
label={label} | ||
multiline={true} | ||
onChange={stringChangeHandler(onChange)} | ||
rowsMax={8} | ||
value={value} | ||
variant="outlined" | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/components/Launch/LaunchForm/inputHelpers/struct.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { stringifyValue } from 'common/utils'; | ||
import { Core, Protobuf } from 'flyteidl'; | ||
import { InputValue } from '../types'; | ||
import { structPath } from './constants'; | ||
import { ConverterInput, InputHelper, InputValidatorParams } from './types'; | ||
import { extractLiteralWithCheck } from './utils'; | ||
|
||
type PrimitiveType = string | number | boolean | null | object; | ||
|
||
function asValueWithKind(value: Protobuf.IValue): Protobuf.Value { | ||
return value instanceof Protobuf.Value | ||
? value | ||
: Protobuf.Value.create(value); | ||
} | ||
|
||
function protobufValueToPrimitive( | ||
value: Protobuf.IValue | ||
): PrimitiveType | PrimitiveType[] { | ||
const valueWithKind = asValueWithKind(value); | ||
const { kind } = valueWithKind; | ||
switch (kind) { | ||
case 'nullValue': | ||
return null; | ||
case 'structValue': | ||
if (valueWithKind.structValue == null) { | ||
throw new Error('Unexpected empty structValue field'); | ||
} | ||
return protobufStructToObject(valueWithKind.structValue); | ||
case 'listValue': | ||
if (valueWithKind.listValue == null) { | ||
throw new Error('Unexpected empty listValue field'); | ||
} | ||
return protobufListToArray(valueWithKind.listValue); | ||
case undefined: | ||
throw new Error('Unexpected missing Value.kind'); | ||
default: | ||
return valueWithKind[kind]; | ||
} | ||
} | ||
|
||
function primitiveToProtobufValue(value: any): Protobuf.IValue { | ||
if (value == null) { | ||
return { nullValue: Protobuf.NullValue.NULL_VALUE }; | ||
} | ||
if (Array.isArray(value)) { | ||
return { listValue: { values: value.map(primitiveToProtobufValue) } }; | ||
} | ||
switch (typeof value) { | ||
case 'boolean': | ||
return { boolValue: !!value }; | ||
case 'number': | ||
return { numberValue: value }; | ||
case 'string': | ||
return { stringValue: value }; | ||
case 'object': | ||
return { structValue: objectToProtobufStruct(value) }; | ||
default: | ||
throw new Error(`Unsupported value type: ${typeof value} `); | ||
} | ||
} | ||
|
||
function protobufListToArray(list: Protobuf.IListValue): PrimitiveType[] { | ||
if (!list.values) { | ||
return []; | ||
} | ||
|
||
return list.values.map(protobufValueToPrimitive); | ||
} | ||
|
||
function protobufStructToObject(struct: Protobuf.IStruct): Dictionary<any> { | ||
if (struct.fields == null) { | ||
return {}; | ||
} | ||
|
||
return Object.entries(struct.fields).reduce<Dictionary<any>>( | ||
(out, [key, value]) => { | ||
out[key] = protobufValueToPrimitive(value); | ||
return out; | ||
}, | ||
{} | ||
); | ||
} | ||
|
||
function objectToProtobufStruct(obj: Dictionary<any>): Protobuf.IStruct { | ||
const fields = Object.entries(obj).reduce<Record<string, Protobuf.IValue>>( | ||
(out, [key, value]) => { | ||
try { | ||
out[key] = primitiveToProtobufValue(value); | ||
return out; | ||
} catch (e) { | ||
throw new Error( | ||
`Failed to convert value ${key} to Protobuf.Value: ${e}` | ||
); | ||
} | ||
}, | ||
{} | ||
); | ||
|
||
return { fields }; | ||
} | ||
|
||
function fromLiteral(literal: Core.ILiteral): InputValue { | ||
const structValue = extractLiteralWithCheck<Protobuf.IStruct>( | ||
literal, | ||
structPath | ||
); | ||
|
||
return stringifyValue(protobufStructToObject(structValue)); | ||
} | ||
|
||
function toLiteral({ value }: ConverterInput): Core.ILiteral { | ||
let parsedObject: Dictionary<any>; | ||
|
||
if (typeof value === 'object') { | ||
parsedObject = value; | ||
} else { | ||
const stringValue = | ||
typeof value === 'string' ? value : value.toString(); | ||
|
||
try { | ||
parsedObject = JSON.parse(stringValue); | ||
if (typeof parsedObject !== 'object') { | ||
throw new Error(`Result was of type: ${typeof parsedObject}`); | ||
} | ||
} catch (e) { | ||
throw new Error(`Value did not parse to an object`); | ||
} | ||
} | ||
|
||
return { scalar: { generic: objectToProtobufStruct(parsedObject) } }; | ||
} | ||
|
||
function validate({ value }: InputValidatorParams) { | ||
if (typeof value !== 'string') { | ||
throw new Error('Value is not a string'); | ||
} | ||
|
||
try { | ||
JSON.parse(value); | ||
} catch (e) { | ||
throw new Error(`Value did not parse to an object: ${e}`); | ||
} | ||
} | ||
|
||
export const structHelper: InputHelper = { | ||
fromLiteral, | ||
toLiteral, | ||
validate | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.